Load libraries
Loading required package: Matrix
Loading required package: igraph
package ‘igraph’ was built under R version 3.5.3
Attaching package: ‘igraph’
The following objects are masked from ‘package:stats’:
decompose, spectrum
The following object is masked from ‘package:base’:
union
Attaching package: ‘pagoda2’
The following objects are masked _by_ ‘.GlobalEnv’:
armaCor, gene.vs.molecule.cell.filter, namedNames, read.10x.matrices, show.app
Attaching package: ‘dplyr’
The following objects are masked from ‘package:igraph’:
as_data_frame, groups, union
The following objects are masked from ‘package:stats’:
filter, lag
The following objects are masked from ‘package:base’:
intersect, setdiff, setequal, union
Attaching package: ‘conos’
The following objects are masked _by_ ‘.GlobalEnv’:
plotClusterBarplots, plotComponentVariance, plotDEheatmap
Loading required package: ggplot2
Attaching package: ‘ggplot2’
The following object is masked _by_ ‘.GlobalEnv’:
qplot
Attaching package: ‘cowplot’
The following object is masked from ‘package:ggplot2’:
ggsave
Load kidney data
source("~/m/pagoda2/R/helpers.r")
path <- "/d0-mendel/home/pkharchenko/ninib/NB/kidney/GSE114530"
pl <- paste(path,list.files(path=path),sep='/')
names(pl) <- list.files(path=path)
pl <- pl[!grepl('w9',names(pl))]
min.size <- 1e3;
cdl <- read.10x.matrices(pl,n.cores=30)
cdl <- lapply(cdl,function(d) d[,colSums(d)>=min.size,drop=F])
mdl <- lapply(pl,function(d) read.delim(paste(d,'annot.csv',sep='/'),sep=',',header=T,stringsAsFactors = F))
kidney.cell.f <- setNames(unlist(lapply(mdl,function(d) d[,2])),unlist(lapply(pagoda2:::sn(names(mdl)),function(n) paste(n,mdl[[n]][,1],sep='_'))))
Get cell cycle genes
cc.genes <- readRDS("~pkharchenko/m/ninib/NB/cc.genes.rds")
Generate p2 apps, omitting cell cycle genes and re-calculating variance norm
cdl.p2 <- lapply(cdl,function(cd) {
cd <- cd[!rownames(cd)%in%cc.genes[,2], ]
basicP2proc(cd,n.cores=30,make.geneknn = FALSE,get.tsne = T,get.largevis = FALSE)
})
5755 cells, 32871 genes; normalizing ... using plain model winsorizing ... log scale ... done.
calculating variance fit ... using gam 689 overdispersed genes ... 689persisting ... done.
running PCA using 3000 OD genes .... done
creating space of type angular done
adding data ... done
building index ... done
querying ... done
running tSNE using 30 cores:
7114 cells, 32871 genes; normalizing ... using plain model winsorizing ... log scale ... done.
calculating variance fit ...
#cdl.p2 <- lapply(cdl,basicP2proc,n.cores=20,min.cells.per.gene=-1, get.largevis=F, get.tsne=T, make.geneknn=F,n.odgenes=2e3, nPcs=50)
Load adrenal data This contains adrenal data
ncdconA <- readRDS("~pkharchenko/m/ninib/NB/ncdconA.rds")
neww_annot <- readRDS("~pkharchenko/m/ninib/NB/neww_annot.rds")
ap2 <- readRDS("~pkharchenko/m/ninib/NB/figures/fig1/ap2.p2.rds")
Recalculate without cell cycle genes and cluster 10
x <- ap2$misc$rawCounts
f <- ap2$clusters$PCA[[1]];
x <- x[!rownames(x) %in% names(f)[f==10], !colnames(x) %in% cc.genes[,2]]
ap2b <- basicP2proc(t(x),n.cores=30,make.geneknn = FALSE,get.tsne = T,get.largevis = FALSE)
7706 cells, 32696 genes; normalizing ... using plain model winsorizing ... log scale ... done.
calculating variance fit ... using gam 2220 overdispersed genes ... 2220persisting ... done.
running PCA using 3000 OD genes .... done
creating space of type angular done
adding data ... done
building index ... done
querying ... done
running tSNE using 30 cores:
cona <- Conos$new(c(cdl.p2,list("Adr"=ap2b)))
cona$buildGraph(k=15,k.self=5,k.self.weigh=0.1,ncomps=30,n.odgenes=2e3,space='PCA')
found 0 out of 10 cached PCA space pairs ... running 10 additional PCA space pairs done
inter-sample links using mNN done
local pairs local pairs done
building graph ..done
cona$findCommunities(method=leiden.community,resolution=1)
cona$embedGraph(method='UMAP',seed=0)
Convert graph to adjacency list...
Done
Estimate nearest neighbors and commute times...
Estimating hitting distances: 08:23:25.
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Done.
Estimating commute distances: 08:23:29.
Hashing adjacency list: 08:23:29.
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
****************************************
Done.
**********|
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
Estimating distances: 08:23:30.
**************************************************|
Done
Done.
All done!: 08:23:33.
Done
Estimate UMAP embedding...
Maximal number of estimated neighbors is 37. Consider increasing min.visited.verts, min.prob or min.prob.lower.
08:23:33 UMAP embedding parameters a = 0.0267 b = 0.7906
08:23:33 Read 34141 rows and found 1 numeric columns
08:23:35 Commencing smooth kNN distance calibration using 64 threads
08:23:39 Initializing from normalized Laplacian + noise
08:23:42 Commencing optimization for 1000 epochs, with 869504 positive edges using 64 threads
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
08:24:03 Optimization finished
Done
cona$misc$embeddings <- list("UMAP"=cona$embedding)
size <- 0.1; alpha <- 0.5;
tissuef <- cona$getDatasetPerCell();
tissuef <- as.factor(setNames(ifelse(grepl('^w',names(tissuef)),'kidney','adrenal'),names(tissuef)))
pl <- list(cona$plotGraph(groups=tissuef, size=size, alpha=alpha,title='tissue',mark.groups=F),
cona$plotGraph(groups=ap2$clusters$PCA[[1]],size=size, alpha=alpha,title='adrenal clusters',plot.na=F),
cona$plotGraph(groups = kidney.cell.f, size=size, alpha=alpha,title='kidney',font.size=c(3,5),plot.na=F),
cona$plotGraph(groups = neww_annot, size=size, alpha=alpha,title='adrenal',plot.na=F))
plot_grid(plotlist=pl,nrow=2)

cona$plotGraph(groups=setNames(ap2$clusters$PCA[[1]]==7,names(ap2$clusters$PCA[[1]])),size=size, alpha=alpha,title='adrenal clusters',plot.na=F)

annot <- readRDS("/d0-mendel/home/meisl/Workplace/neuroblastoma/cell.annotation.Jan2020.rds")$cellano
annot <- setNames(as.character(annot),names(annot))
annot[annot=='unknown'] <- 'Erythroid'
annot[annot=='Plasmacytoid'] <- 'pDC'
annot[grep('Cytotoxic',annot)] <- 'Tcyto'
annot[grep('T helper',annot)] <- 'Th'
annot[grep('NK',annot)] <- 'NK'
annot[grep('Bcell',annot)] <- 'B'
annot[grep('PlasmaCell',annot)] <- 'Plasma'
annot[grep('Mast',annot)] <- 'Mast'
annot[grep("Bridge",annot)] <- 'SCP-like'
dannot <- as.factor(annot)
annot[grep("SOX11|Stress|euronal|Prolif",annot)] <- 'Noradrenergic'
annot[grep("Bridge",annot)] <- 'SCP-like'
annot <- as.factor(annot);
# renamings
levels(annot) <- gsub("Noradrenergic","Adrenergic",levels(annot))
Load annotations and palettes:
load("../figures/fig1/annotations.RData")
Align everything together:
conb <- Conos$new(c(cdl.p2,ncdconA$samples[grepl("^NB",names(ncdconA$samples))],list("Adr"=ap2b)))
conb$buildGraph(k=15,k.self=20,k.self.weigh=0.5,ncomps=30,n.odgenes=2e3,space='PCA')
found 231 out of 231 cached PCA space pairs ... done
inter-sample links using mNN done
local pairs local pairs done
building graph ..done
conb$findCommunities(method=leiden.community,resolution=1)
conb$embedGraph(method='UMAP',seed=0, min.dist=0.01, spread=10, min.prob.lower=1e-3,n.epochs=2e3)
Convert graph to adjacency list...
Done
Estimate nearest neighbors and commute times...
Estimating hitting distances: 11:06:27.
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Done.
Estimating commute distances: 11:06:34.
Hashing adjacency list: 11:06:34.
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
***************************************
Done.
***********|
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
Estimating distances: 11:06:37.
**************************************************|
Done
Done.
All done!: 11:07:00.
Done
Estimate UMAP embedding...
11:07:01 UMAP embedding parameters a = 0.05039 b = 0.7915
11:07:01 Read 107335 rows and found 1 numeric columns
11:07:05 Commencing smooth kNN distance calibration using 64 threads
11:07:13 Initializing from normalized Laplacian + noise
11:07:20 Commencing optimization for 2000 epochs, with 4186840 positive edges using 64 threads
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
11:08:33 Optimization finished
Done
conb$misc$embeddings <- list("UMAP"=conb$embedding)
conb$misc$embeddings$largeVis <- conb$embedGraph(method='largeVis',alpha=3, sgd_batches = 3e8)
Estimating embeddings.
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
conb$misc$embeddings$UMAP2 <- conb$embedGraph(method='UMAP', min.dist=0.01, spread=15, min.prob.lower=1e-3,n.epochs=2e3)
Convert graph to adjacency list...
Done
Estimate nearest neighbors and commute times...
Estimating hitting distances: 11:16:25.
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Done.
Estimating commute distances: 11:16:32.
Hashing adjacency list: 11:16:32.
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
******************************
Done.
********************|
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
Estimating distances: 11:16:34.
**************************************************|
Done
Done.
All done!: 11:16:57.
Done
Estimate UMAP embedding...
11:16:58 UMAP embedding parameters a = 0.02659 b = 0.7912
11:16:58 Read 107335 rows and found 1 numeric columns
11:17:02 Commencing smooth kNN distance calibration using 64 threads
11:17:09 Initializing from normalized Laplacian + noise
11:17:17 Commencing optimization for 2000 epochs, with 4186840 positive edges using 64 threads
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
11:18:27 Optimization finished
Done
size <- 0.1; alpha <- 0.1;
tissuef <- conb$getDatasetPerCell();
tissuef <- setNames(as.character(tissuef),names(tissuef))
tissuef[grep("^w",tissuef)] <- 'kidney'
tissuef[grep("^Adr",tissuef)] <- 'adrenal'
tissuef[grep("^NB",tissuef)] <- 'NB'
pl <- list(conb$plotGraph(groups=tissuef, size=size, alpha=alpha,title='tissue',mark.groups=F),
conb$plotGraph(groups=annot,size=size, alpha=alpha,title='neuroblastoma',plot.na=F),
conb$plotGraph(groups = kidney.cell.f, size=size, alpha=alpha,title='kidney',font.size=c(3,5),plot.na=F),
#conb$plotGraph(groups = neww_annot, size=size, alpha=alpha,title='adrenal',plot.na=F))
conb$plotGraph(groups = ap2$clusters$PCA[[1]], size=size*4, alpha=alpha,title='adrenal',plot.na=F))
plot_grid(plotlist=pl,nrow=2)

ninib_bannot <- readRDS("~pkharchenko/m/ninib/NB/ninib_bannot.rds")
Update adrenal annotation
adr.ann <- setNames(as.character(neww_annot),names(neww_annot));
adr.ann[adr.ann=='Immune cells'] <- 'Kidney cells'
adr.ann[adr.ann=='Vessels'] <- 'Endothelial'
adr.ann[adr.ann=='Blood cells'] <- 'Erythroid'
adr.ann[adr.ann=='Pericytes'] <- 'Mesenchymal'
adr.ann[adr.ann=='Sympatoblasts'] <- 'Sympathoblasts'
adr.ann[adr.ann=='Schwann cells'] <- 'SCPs'
cl <- ap2$clusters$PCA[[1]]
adr.ann[names(adr.ann) %in% names(cl)[cl=='18']] <- "Myeloid"
adr.ann[names(adr.ann) %in% names(cl)[cl=='11']] <- "Lymphoid"
adr.ann[names(adr.ann) %in% names(cl)[cl=='14']] <- "Kidney cells"
adr.ann <- as.factor(adr.ann)
set.seed(4)
adr.pal <- sample(rainbow(length(levels(annot)),v=0.95,s=0.85));
adr.palf <- function(n) return(adr.pal)
With density contour
linetype <- 1; color='grey20';
kd <- ks::kde(conb$embedding[rownames(conb$embedding) %in% names(annot)[annot=='Noradrenergic'],], compute.cont=TRUE)
cn <- with(kd, contourLines(x=eval.points[[1]], y=eval.points[[2]],z=estimate, levels=cont["10%"])[[1]])
cn <- geom_path(aes(x, y), data=data.frame(cn),linetype = linetype , color=color);
kd <- ks::kde(conb$embedding[rownames(conb$embedding) %in% names(annot)[annot=='Mesenchymal'],], compute.cont=TRUE)
cm <- with(kd, contourLines(x=eval.points[[1]], y=eval.points[[2]],z=estimate, levels=cont["10%"])[[1]])
cm <- geom_path(aes(x, y), data=data.frame(cm),linetype = linetype , color=color);
kd <- ks::kde(conb$embedding[rownames(conb$embedding) %in% names(annot)[annot=='SCP-like'],], compute.cont=TRUE, approx.cont = F)
#kd <- ks::kde(conb$embedding[rownames(conb$embedding) %in% names(ninib_bannot)[ninib_bannot=='Fibroblast_Bridge'],], compute.cont=TRUE)
cb <- with(kd, contourLines(x=eval.points[[1]], y=eval.points[[2]],z=estimate, levels=cont["50%"])[[1]])
cb <- geom_path(aes(x, y), data=data.frame(cb),linetype = linetype , color=color);
size <- 0.1; alpha <- 0.1;
tissuef <- conb$getDatasetPerCell();
tissuef <- setNames(as.character(tissuef),names(tissuef))
tissuef[grep("^w",tissuef)] <- 'kidney'
tissuef[grep("^Adr",tissuef)] <- 'adrenal'
tissuef[grep("^NB",tissuef)] <- 'NB'
raster <- T;
#af <- setNames(names(tissuef) %in% names(ninib_bannot)[ninib_bannot=='Fibroblast_Bridge'],names(tissuef))
pl <- list(conb$plotGraph(groups=tissuef, size=size, alpha=alpha,raster=raster, title='tissue',mark.groups=F)+ theme(legend.position=c(0.85, 0.15)) + guides(color=guide_legend(override.aes = list(size=3,alpha=0.8),title='Tissue:')),
conb$plotGraph(groups=annot,size=size, alpha=alpha,raster=raster,title='neuroblastoma',plot.na=F,palette=annot.pal),
conb$plotGraph(groups = kidney.cell.f, size=size, alpha=alpha,raster=raster,title='kidney',font.size=c(3,5),plot.na=F),
conb$plotGraph(groups = adr.ann, size=size*4, alpha=alpha*2,raster=raster,title='adrenal',plot.na=F,palette=adr.palf,font.size=c(4,5)))
#conb$plotGraph(groups = ap2$clusters$PCA[[1]], size=size*4, alpha=alpha*2,title='adrenal',plot.na=F))
pl <- lapply(pl,function(p) p+cn+cm+cb+ xlim(range(conb$embedding[,1]))+ylim(range(conb$embedding[,2])))
pp <- plot_grid(plotlist=pl,nrow=2)
pp

pdf('combined.pdf',width=10,heigh=10); print(pp); dev.off();
null device
1
invisible(lapply(1:length(pl),function(i) { pdf(paste('panel',i,'pdf',sep='.'),width=4,height=4); print(pl[[i]]); dev.off()}))
Restricted window plot for the main figure:
size <- 0.3; alpha <- 0.1;
raster <- T;
xlim <- c(-66,40); ylim <- c(-63,0)
#af <- setNames(names(tissuef) %in% names(ninib_bannot)[ninib_bannot=='Fibroblast_Bridge'],names(tissuef))
pl <- list(conb$plotGraph(groups=tissuef, size=size, alpha=alpha,raster=raster, raster.width=6.6,raster.height=4, title='tissue',mark.groups=F),
conb$plotGraph(groups=annot,size=size, alpha=alpha,raster=raster,title='neuroblastoma', raster.width=6.6,raster.height=4,plot.na=F,palette=annot.pal),
#conb$plotGraph(groups = kidney.cell.f, size=size, alpha=alpha,raster=raster,title='kidney',font.size=c(3,5),plot.na=F),
conb$plotGraph(groups = adr.ann, size=size*4, alpha=alpha*2,raster=raster,title='adrenal', raster.width=6.6,raster.height=4,plot.na=F,palette=adr.palf,font.size=c(4,5)))
#conb$plotGraph(groups = ap2$clusters$PCA[[1]], size=size*4, alpha=alpha*2,title='adrenal',plot.na=F))
#pl <- lapply(pl,function(p) p+cn+cm+cb+ coord_cartesian(xlim,ylim,clip="on"))
# pl <- lapply(pl,function(p) p+cn+cm+cb+scale_x_continuous(expand = c(0, 0)) +
# scale_y_continuous(expand = c(0, 0)) + xlim(xlim)+ylim(ylim)
# )
pl <- lapply(pl,function(p) p+cn+cm+cb+coord_cartesian(expand = c(0, 0), xlim=xlim,ylim=ylim) +xlim(xlim)+ylim(ylim))
pp <- plot_grid(plotlist=pl,ncol=1)
the condition has length > 1 and only the first element will be usedthe condition has length > 1 and only the first element will be usedRemoved 68617 rows containing missing values (geom_point_rast).the condition has length > 1 and only the first element will be usedthe condition has length > 1 and only the first element will be usedRemoved 49920 rows containing missing values (geom_point_rast).Removed 13 rows containing missing values (geom_label_repel).the condition has length > 1 and only the first element will be usedthe condition has length > 1 and only the first element will be usedRemoved 5128 rows containing missing values (geom_point_rast).Removed 5 rows containing missing values (geom_label_repel).
pdf('combined_narrow.pdf',width=3.5,heigh=6); print(pp); dev.off();
png
2
pp

conb$plotGraph(gene='NR5A1',alpha=0.4,size=0.1)

jgrp <- conb$findCommunities(method=leiden.community,r=4)$groups
conb$plotGraph(alpha=0.1,size=0.1,mark.groups=T,groups=jgrp)

Illustrate similarity between bridge and SCPs:
Identify genes that set both populations apart from others:
fac <- conb$getDatasetPerCell()
fac <- setNames(rep('other',length(fac)),names(fac))
#fac[names(fac) %in% c(names(adr.ann)[adr.ann=='Schwann cells'], names(annot)[annot=='SCP-like'])] <- 'SCPs'
schwann.de <- conb$getDifferentialGenes(groups=fac,n.cores=30,append.auc=TRUE,z.threshold=0,upregulated.only=T)
source("~/m/p2/conos/R/plot.R")
sfac <- fac; sfac <- sfac[unlist(tapply(1:length(fac),fac,sample,min(table(fac))))]
pp <- plotDEheatmap(conb,sfac,schwann.de,n.genes.per.cluster = 30 ,show.gene.clusters=T,row.label.font.size = 6,column.metadata = list(tissue=tissuef))
#pdf(file='adr.heatmap.pdf',width=7,height=15); print(pp); dev.off();
pp
fac2 <- conb$getDatasetPerCell()
fac2 <- setNames(rep('other',length(fac2)),names(fac2))
#fac[names(fac) %in% c(names(adr.ann)[adr.ann=='Schwann cells'], names(annot)[annot=='SCP-like'])] <- 'SCPs'
fac2[names(fac2) %in% c(names(jgrp)[jgrp=='37'])] <- 'SCPs'
schwann.de2 <- conb$getDifferentialGenes(groups=fac2,n.cores=30,append.auc=TRUE,z.threshold=0,upregulated.only=T)
source("~/m/p2/conos/R/plot.R")
sfac <- as.factor(fac2); sfac <- droplevels(sfac[unlist(tapply(1:length(fac),fac,sample,min(table(fac))))])
pp <- plotDEheatmap(conb,sfac,schwann.de2,n.genes.per.cluster = 30 ,show.gene.clusters=T,row.label.font.size = 7,column.metadata = list(tissue=tissuef))
#pdf(file='adr.heatmap.pdf',width=7,height=15); print(pp); dev.off();
pp

There’s a group within the ‘other’ that seems to share the SCP pattern …
conb$plotGraph(gene='PLP1',alpha=0.4,size=0.1)

conb$plotGraph(gene='S100B',alpha=0.4,size=0.1)

qplot <- function(g, con.obj, ann) {
#cat(g,' ')
x <- lapply(con.obj$samples[5:10],function(r) { if(g%in% colnames(r$counts)) { r$counts[,g] } else { return(NULL) } })
if(length(unlist(x))<1) stop('gene ',g,' is not found')
df <- data.frame(val=unlist(x),cell=unlist(lapply(x,names)))
df$cluster <- ann[match(df$cell,names(ann))]
df <- na.omit(df)
mv <- max(tapply(df$val,df$cluster,quantile,p=0.8),tapply(df$val,df$cluster,mean))*1.5
p <- ggplot(df,aes(x=cluster,y=val,color=cluster))+geom_boxplot(outlier.shape = NA)+ stat_summary(fun.data=mean_se,geom="pointrange", color="black")+ylab(g)+ggtitle(g)+guides(colour=FALSE)+ theme(axis.text.x = element_text(angle = 90, hjust = 1,vjust=0.5))+coord_cartesian(ylim=c(0,mv));
p
}
markers <- c(as.character(schwann.de2$SCPs %>% arrange(-AUC) %>% head(8) %>% '$'('Gene')),'SOX10','MIA')
pl <- lapply(markers,qplot,conb,jgrp)
plot_grid(plotlist=pl,ncol=2)

It’s cluster 35, which is the extension of the bridge into the fibroblasts … most likely as a result of clustering uncertainty. The cluster should be better defined on NB alone …
Check expression on the NB embedding
ncdcon <- readRDS("~pkharchenko/m/ninib/NB/ncdcon.rds")
p1 <- ncdcon$plotGraph(alpha=0.03,size=0.5,groups=ncdcon$clusters$rleiden$groups,plot.na=F,mark.groups=T)
p2 <- ncdcon$plotGraph(alpha=0.03,size=0.5,plot.na=F,mark.groups=T,groups=fac)
p3 <- ncdcon$plotGraph(alpha=0.03,size=0.5,groups=fac2,plot.na=F,mark.groups=T)
plot_grid(plotlist=list(p1,p2,p3),nrow=1)

pl <- lapply(markers,qplot,ncdcon,ncdcon$clusters$rleiden$groups)
plot_grid(plotlist=pl,ncol=2)

Here to, while cluster 1-5 captures most of the SCPs, part of the signature is still notable in the cluster 5-1 that connects to the messenchymal tumor cells
Also, in doing DE on fac2, the adrenal and NB contributions to the SCP population are very unbalanced. It’s probably best to run the two separately and intersect the high-AUC genes:
fac3.adr <- conb$getDatasetPerCell()
fac3.adr <- setNames(rep('other',length(fac3.adr)),names(fac3.adr))
# remove NB SCP-like cells
fac3.adr <- fac3.adr[!names(fac3.adr) %in% c(names(jgrp)[jgrp=='35'], names(annot)[annot=='SCP-like'], intersect(names(annot),names(jgrp)[jgrp=='37']))]
fac3.adr[names(fac3.adr) %in% c(names(adr.ann)[adr.ann=='Schwann cells'])] <- 'adrenal SCP'
#fac3.adr[names(fac3.adr) %in% c(names(jgrp)[jgrp=='37'])] <- 'SCPs' # use a more restrictive cluster-based definition
fac3.adr <- droplevels(as.factor(fac3.adr))
table(fac3.adr,conb$getDatasetPerCell()[names(fac3.adr)])
fac3.adr Adr NB01 NB02 NB09 NB11 NB12 NB13 NB15 NB16 NB17 NB18 NB19 NB20 NB21 NB22 NB23 NB24 NB26 w11 w13 w16 w18
adrenal SCP 46 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
other 7646 979 1373 6529 10324 5149 17274 5429 2814 1553 4655 3242 534 480 1116 2586 2956 5130 5552 7072 8071 4866
And NB only cells
# factor for just the adrenal SCP cells
fac3.nb <- conb$getDatasetPerCell()
fac3.nb <- setNames(rep('other',length(fac3.nb)),names(fac3.nb))
# remove NB SCP-like cells
fac3.nb <- fac3.nb[!names(fac3.nb) %in% c(names(jgrp)[jgrp=='35'], names(adr.ann)[adr.ann=='Schwann cells'])]
fac3.nb[names(fac3.nb) %in% c(intersect(names(annot),names(jgrp)[jgrp=='37']))] <- 'SCP-like NB'
fac3.nb <- droplevels(as.factor(fac3.nb))
table(fac3.nb,conb$getDatasetPerCell()[names(fac3.nb)])
schwann.de3.nb <- conb$getDifferentialGenes(groups=fac3.nb,n.cores=30,append.auc=TRUE,z.threshold=0,upregulated.only=T)
Join tables and separate common and distinct markers
require(magrittr)
df <- full_join(schwann.de3.adr[['adrenal SCP']], schwann.de3.nb[['SCP-like NB']], by='Gene',suffix=c('.adr','.nb'))
Column `Gene` joining factors with different levels, coercing to character vector
df$minAUC <- pmin(df$AUC.adr,df$AUC.nb)
df$maxAUC <- pmax(df$AUC.adr,df$AUC.nb,na.rm=T)
df %<>% arrange(-minAUC,maxAUC) %>% select(Gene,minAUC,maxAUC,AUC.nb,AUC.adr)
head(df,100)
tail(df,100)
Show combined expression pattern
genes.nb <- tail(df[is.na(df$AUC.adr),],7)$Gene #7
genes.adr <- tail(df[is.na(df$AUC.nb),],6)$Gene
genes.adr <- unique(c(genes.adr,'ASCL1'))
#genes.common <- c(head(df,14)$Gene,'SOX10','ASCL1','EGR2','DHH','OCT4','ISL1') #14
genes.common <- unique(c(head(df,13)$Gene,'SOX10',"FOXD3" , "PMP2", "LGI4", "SEMA3B")) #14
genes <- unique(c(genes.common,genes.adr,genes.nb))
#cell.factor <- as.factor(c(setNames(rep('NB SCP-like',length(c1)),c1),setNames(rep("adrenal SCP",length(c2)),c2)))
cell.factor <- c(setNames(as.character(fac3.nb),names(fac3.nb)),setNames(as.character(fac3.adr),names(fac3.adr)))
cell.factor <- cell.factor[unique(names(cell.factor))]
oc <- names(cell.factor)[cell.factor=='other']; cell.factor[oc] <- paste('other',as.character(tissuef[oc]),sep=' ')
cell.factor <- factor(cell.factor,levels=unique(cell.factor)[c(5,3,2,4,1)])
# omit kidney
cell.factor[cell.factor=='other kidney'] <- NA; cell.factor <- droplevels(na.omit(cell.factor))
xa <- do.call(rbind,lapply(pagoda2:::sn(genes),function(gene) conos:::getGeneExpression(conb,gene)))
Because the numbers of cells are so unbalanced, we’ll randomly collapse larger groups to approximately match the number of cells in the smaller groups
xc <- do.call(cbind, tapply(1:ncol(xa),cell.factor[colnames(xa)],function(ii) {
bin.size <- ceiling(length(ii)/min(table(cell.factor)))
nbins <- ceiling(length(ii)/bin.size)
if(bin.size >1 ) {
bf <- sample(rep(1:nbins,bin.size))[1:length(ii)]
#x <- do.call(cbind,tapply(1:length(ii),as.factor(bf),function(x) rowMeans(xa[,ii[x]])))
x <- do.call(cbind,tapply(1:length(ii),as.factor(bf),function(x) xa[,sample(ii[x],1),drop=F]))
#colnames(x) <- colnames(xa)[ii][1:ncol(x)]
x
} else {
return(xa[,ii])
}
}))
# trim
expression.quantile <- 0.75
x <- na.omit(xc)
#x <- na.omit(x[,names(cell.factor)])
x <- t(apply(x, 1, function(xp) {
if(expression.quantile<1) {
qs <- quantile(xp,c(1-expression.quantile,expression.quantile))
if(diff(qs)==0) { # too much, set to adjacent values
xps <- unique(xp)
if(length(xps)<3) { qs <- range(xp) } # only two values, just take the extremes
xpm <- median(xp)
if(sum(xp<xpm) > sum(xp>xpm)) { # more common to have values below the median
qs[1] <- max(xp[xp<xpm])
} else { # more common to have values above the median
qs[2] <- min(xps[xps>xpm]) # take the next one higher
}
}
xp[xp<qs[1]] <- qs[1]
xp[xp>qs[2]] <- qs[2]
}
xp <- xp-min(xp);
if(max(xp)>0) xp <- xp/max(xp);
xp
# qs <- quantile(xp,c(1-expression.quantile,expression.quantile))
# #xp[xp<qs[1]] <- qs[1]
# if(qs[2]>0) {
# xp[xp>qs[2]] <- qs[2]
# }
# xp-min(xp);
# xpr <- diff(range(xp));
# if(xpr>0) xp <- xp/xpr;
# xp
}))
require(ComplexHeatmap)
# column annotation
an <- data.frame(cells=cell.factor[colnames(x)],row.names = colnames(x))
# row annotation
gpal <- c("common SCP genes"='grey30','adrenal'='green','NB'='orange');
rdf <- data.frame(genes=rep(names(gpal),c(length(genes.common),length(genes.adr),length(genes.nb))),name=c(genes.common,genes.adr,genes.nb));
rdf <- rdf[!duplicated(rdf$name),];
rdf <- data.frame(genes=rdf$genes,row.names=rdf$name)
#rownames(rdf) <- c(genes.common,genes.adr,genes.nb);
rdf <- rdf[rownames(x),,drop=F]
ra <- ComplexHeatmap::HeatmapAnnotation(df=rdf,which='row',show_annotation_name=FALSE, show_legend=FALSE, border=T,col=list(genes=gpal))
ra <- ComplexHeatmap::HeatmapAnnotation(genes=anno_block(gp=gpar(fill=NA,col=NA),labels=names(gpal[levels(rdf$genes)]),labels_gp = gpar(fontsize=16) ),which='row',show_annotation_name=FALSE, show_legend=FALSE, border=T)
#ta <- ComplexHeatmap::HeatmapAnnotation(df=an,border=T,show_legend = T,col=list(cells=c('adrenal SCP'='darkorchid4','SCP-like NB'='red','other NB'='orange','other adrenal'='green','other kidney'='royalblue3')))
gpal <- c('adrenal SCP'='darkorchid4','SCP-like NB'='red','other NB'='orange','other adrenal'='green');
ta <- ComplexHeatmap::HeatmapAnnotation(cells=anno_block(gp=gpar(fill=NA,col=NA), labels=names(gpal), labels_rot = 35, labels_gp = gpar(just=-1, fontsize=14)))
#ComplexHeatmap::Heatmap(x,name='Expression',col=colorRampPalette(c('dodgerblue1','grey95','indianred1'))(1024), left_annotation=ra, cluster_rows = F, cluster_columns = FALSE,show_row_names = T, show_column_names = F,top_annotation = ta,border=T)
set.seed(0)
hm <- ComplexHeatmap::Heatmap(x,name='Expression',col=colorRampPalette(c('dodgerblue1','grey95','indianred1'))(1024), left_annotation=ra, cluster_rows = F, cluster_columns = FALSE,show_row_names = T, show_column_names = F,top_annotation = ta,border=T,column_split=an[colnames(x),1], row_split=rdf[,1], row_gap = unit(1, "mm"), column_gap = unit(1, "mm"),row_names_gp = grid::gpar(fontsize = 8),column_title = NULL, row_title = NULL, heatmap_legend_param = list(legend_direction='horizontal'),use_raster = T,raster_device = "CairoPNG")
hmp <- draw(hm, heatmap_legend_side = "bottom")

pdf(file='scp.markers2.pdf',width=4,height=7); print(hmp); dev.off();
png
2
Cell type similarity matrix
Let’s go with the simplest thing
source("~pkharchenko/m/pavan/DLI/conp2.r")
f1 <- setNames(as.character(adr.ann),names(adr.ann))
#f1[f1 %in% c("Monocytes","Mast",'mDC')] <- 'Myeloid'
f2 <- setNames(as.character(annot),names(annot))
f2[f2 %in% c("B","ILC3",'NK','pDC','Plasma','Tcyto','Th','Treg')] <- 'Lymphoid'
f2[f2 %in% c("Monocytes","Mast",'mDC','Macrophages')] <- 'Myeloid'
groups <- as.factor(c(setNames(paste(f1,'adr',sep=':'),names(f1)),setNames(paste(f2,'nb',sep=':'),names(f2))))
groups[grep('Erythroid|Myeloid|Lymph',groups)] <- NA;
groups[grep('Kidney|Cortex',groups)] <- NA;
groups <- droplevels(na.omit(groups))
table(groups)
groups
Chromaffin cells:adr Endothelial:adr Endothelial:nb Mesenchymal:adr Mesenchymal:nb Myofibroblasts:nb Noradrenergic:nb Pericytes:nb Schwann cells:adr SCP-like:nb
186 907 3584 969 2499 1873 15778 1863 47 987
Sympathoblasts:adr
163
#groups <- unlist(list(annot,adr.ann))
tcd <- cluster.expression.distances(conb,groups=groups,dist='JS',use.aggregated.matrices = T)
sort(tcd[grepl("SCP-like:nb",rownames(tcd)),grepl(":adr",colnames(tcd))],dec=T)
Endothelial:adr Chromaffin cells:adr Sympathoblasts:adr Mesenchymal:adr Schwann cells:adr
0.1520918 0.1472575 0.1331704 0.1212592 0.1180372
Using JS distance
cdist <- tcd[grepl(":nb",rownames(tcd)),grepl(":adr",colnames(tcd))]
#cdist <- tcd[levels(annot),levels(adr.ann)]
#cdist <- t(apply(cdist,1,function(x) (x-min(x))/max(x)))
rownames(cdist) <- gsub(":.*","",rownames(cdist))
colnames(cdist) <- gsub(":.*","",colnames(cdist))
col <- colorRampPalette(c('white','darkblue'))(1024)
#col <- circlize::colorRamp2(c(0.7, 0.9), c("white", "darkblue"))
Heatmap(1-cdist,col=col, cluster_rows = T,cluster_columns = T,border=T)

Correlation-based
tc <- conos:::rawMatricesWithCommonGenes(conb) %>%
lapply(conos:::collapseCellsByType, groups=as.factor(groups), min.cell.count=0) %>%
abind(along=3) %>%
apply(c(1,2),sum,na.rm=T)
lib.size.scale <- 1e6;
tc <- log10(t(tc/pmax(1,rowSums(tc)))*lib.size.scale+1)
tcd <- cor(tc)
Using top overdispersed genes
gns <- ap2$misc$varinfo %>% tibble::rownames_to_column('gene') %>% arrange(-qv) %>% head(2e3) %>% '$'('gene')
tcd <- cor(tc[rownames(tc) %in% gns,])
#tcd <- cor(tc)
sort(tcd[grepl("SCP-like:nb",rownames(tcd)),grepl(":adr",colnames(tcd))],dec=T)
Schwann cells:adr Sympathoblasts:adr Mesenchymal:adr Chromaffin cells:adr Endothelial:adr
0.6923011 0.6276368 0.6195420 0.6143442 0.4371182
cdist <- tcd[grepl(":nb",rownames(tcd)),grepl(":adr",colnames(tcd))]
#cdist <- tcd[levels(annot),levels(adr.ann)]
rownames(cdist) <- gsub(":.*","",rownames(cdist))
colnames(cdist) <- gsub(":.*","",colnames(cdist))
col <- colorRampPalette(c('white','darkblue'))(1024)
col <- circlize::colorRamp2(c(0.4, 0.7), c("white", "darkblue"))
cdist <- cdist[c(4,6,5,1,2,3),c(1,5,4,2,3)]
cm <- Heatmap(cdist,col=col, cluster_rows = F,cluster_columns = F,border=T,name='correlation',column_names_side='top',column_title = 'Adrenal cell types',row_title='NB cell types',column_title_side = 'bottom')
pdf(file='adrenal.match.pdf',width=4,height=3.9); print(cm); dev.off();
null device
1
cm

DEBUG
x <- na.omit(xa)
x <- na.omit(x[,names(cell.factor)])
x <- x>0
annot <- data.frame(clusters=cell.factor[colnames(x)],row.names = colnames(x))
ta <- ComplexHeatmap::HeatmapAnnotation(df=annot,border=T,show_legend = T)
ComplexHeatmap::Heatmap(x,col=colorRampPalette(c('white','gray20'))(1024),cluster_rows = F, cluster_columns = FALSE,show_row_names = T, show_column_names = F,top_annotation = ta,border=T)

source("~/m/p2/conos/R/plot.R")
sfac <- as.factor(fac2); sfac <- droplevels(sfac[unlist(tapply(1:length(fac),fac,sample,min(table(fac))))])
pp <- plotDEheatmap(conb,sfac,schwann.de2,n.genes.per.cluster = 30 ,show.gene.clusters=T,row.label.font.size = 7,column.metadata = list(tissue=tissuef))
#pdf(file='adr.heatmap.pdf',width=7,height=15); print(pp); dev.off();
pp

Try adrenal SCP markers
adr.de <- ap2$getDifferentialGenes(type='PCA',groups=adr.ann,append.auc=TRUE,z.threshold=0,upregulated.only=T)
source("~/m/p2/conos/R/plot.R")
pp <- plotDEheatmap(ap2,adr.ann,adr.de,n.genes.per.cluster = 10 ,show.gene.clusters=T,row.label.font.size = 6,order.clusters = T)
#pdf(file='adr.heatmap.pdf',width=7,height=15); print(pp); dev.off();
pp
source("~/m/p2/conos/R/plot.R")
sfac <- adr.ann; sfac <- droplevels(sfac[sfac %in% c("Chromaffin cells","Schwann cells","Sympathoblasts")])
pp <- plotDEheatmap(ap2,sfac,adr.de,n.genes.per.cluster = 30 ,show.gene.clusters=T,row.label.font.size = 6)
#pdf(file='adr.heatmap.pdf',width=7,height=15); print(pp); dev.off();
pp
Identify genes that distinguish the two populations
d.fac <- conb$getDatasetPerCell()
d.fac <- setNames(rep('other',length(d.fac)),names(d.fac))
d.fac[names(d.fac) %in% c(names(adr.ann)[adr.ann=='Schwann cells'])] <- 'normal SCPs'
d.fac[names(d.fac) %in% c(names(annot)[annot=='SCP-like'])] <- 'tumor SCPs'
tum.norm.de <- conb$getDifferentialGenes(groups=d.fac, n.cores=30,append.auc=TRUE,z.threshold=0,upregulated.only=T)
Combine in a heatmap:
Misc stuff
p1 <- ncdcon$plotGraph(alpha=0.03,size=0.5,groups=annot,plot.na=F,mark.groups=T)
p2 <- ncdcon$plotGraph(alpha=0.03,size=0.5,gene='PRRX1',plot.na=F,mark.groups=T)
p3 <- ncdcon$plotGraph(alpha=0.03,size=0.5,gene='PHOX2B',plot.na=F,mark.groups=T)
plot_grid(plotlist=list(p1,p2,p3),nrow=1)

As factors
str(ascl1e)
Named num [1:81009] 0 0 0 0 0 0 0 0 0 0 ...
- attr(*, "names")= chr [1:81009] "NB26_AAACCTGAGATATGGT-1" "NB26_AAACCTGAGGATGTAT-1" "NB26_AAACCTGAGTCCATAC-1" "NB26_AAACCTGAGTGCGTGA-1" ...
p1 <- ncdcon$plotGraph(alpha=0.03,size=0.5,groups=annot,plot.na=F,mark.groups=T)
p2 <- ncdcon$plotGraph(alpha=0.03,size=0.5,gene='SOX10',plot.na=F,mark.groups=T)
p3 <- ncdcon$plotGraph(alpha=1,size=2,groups=sox10e>0 & phox2be>0,plot.na=F,mark.groups=F,palette=c(adjustcolor('grey90',alpha=0.01),'red'))
plot_grid(plotlist=list(p1,p2,p3),nrow=1)

ncdcon$samples$NB26$plotEmbedding(groups=annot,type='PCA',mark.clusters = T,mark.cluster.cex = 1,embeddingType='tSNE')
using provided groups as a factor

names(dc)[dc]
[1] "NB26_GCTGGGTCAACTGCTA-1"
vc <- names(dc)[dc]; vc <- vc[grepl('^NB',vc)]
df <- data.frame(phox2b=phox2be[vc],prrx1=prrx1e[vc],type=annot[vc],cell=vc)
ggplot(df,aes(x=phox2b,y=prrx1,colour=type)) + geom_point()

plot(ncdcon$embedding[,1],ncdcon$embedding[,2],pch=19,cex=0.2,col=adjustcolor(1,alpha=0.01))
vc <- names(dc)[dc]
points(ncdcon$embedding[rownames(ncdcon$embedding) %in% vc,1],ncdcon$embedding[rownames(ncdcon$embedding) %in% vc,2],col=2)

emb <- acon$embedding
plot(emb[,1],emb[,2],pch=19,cex=0.2,col=adjustcolor(1,alpha=0.01))
vc <- names(dc)[dc]
table(vc %in% rownames(emb))
FALSE TRUE
23 12
points(emb[rownames(emb) %in% vc,1],emb[rownames(emb) %in% vc,2],col=2)

plot(ncdcon$embedding[,1],ncdcon$embedding[,2],pch=19,cex=0.2,col=sccore:::fac2col(annot[match(rownames(ncdcon$embedding),names(annot))]))
acon <- readRDS("~pkharchenko/m/ninib/NB/acon.rds")
#p1 <- acon$plotGraph(alpha=0.01,size=0.2,groups=con$getDatasetPerCell())
p1 <- acon$plotGraph(alpha=0.01,size=0.5,groups=scrubletf,mark.groups=F,plot.na=F,palette=function(n) c('gray','red'),title="Scrublet")
p2 <- acon$plotGraph(alpha=0.01,size=0.5,groups=bannot,plot.na=F,mark.groups=T,title="Annotations")
p3 <- acon$plotGraph(alpha=0.01,size=0.5,gene="MKI67",title="MKI67")
pp <- plot_grid(plotlist=list(p1,p3,p2),nrow=1)
pp
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKTG9hZCBsaWJyYXJpZXMKCmBgYHtyIGVjaG89RkFMU0V9CmxpYnJhcnkocGFnb2RhMikKbGlicmFyeShkcGx5cikKbGlicmFyeShjb25vcykKbGlicmFyeShwYXJhbGxlbCkKbGlicmFyeShjb3dwbG90KQpsaWJyYXJ5KGdncmVwZWwpCmxpYnJhcnkoTWF0cml4KQojc291cmNlKCIvaG9tZS9wa2hhcmNoZW5rby9tL3BhdmFuL0RMSS9jb25wMi5yIikKYGBgCgoKTG9hZCBraWRuZXkgZGF0YQpgYGB7cn0Kc291cmNlKCJ+L20vcGFnb2RhMi9SL2hlbHBlcnMuciIpCnBhdGggPC0gIi9kMC1tZW5kZWwvaG9tZS9wa2hhcmNoZW5rby9uaW5pYi9OQi9raWRuZXkvR1NFMTE0NTMwIgpwbCA8LSBwYXN0ZShwYXRoLGxpc3QuZmlsZXMocGF0aD1wYXRoKSxzZXA9Jy8nKQpuYW1lcyhwbCkgPC0gbGlzdC5maWxlcyhwYXRoPXBhdGgpCnBsIDwtIHBsWyFncmVwbCgndzknLG5hbWVzKHBsKSldCm1pbi5zaXplIDwtIDFlMzsKY2RsIDwtIHJlYWQuMTB4Lm1hdHJpY2VzKHBsLG4uY29yZXM9MzApCmNkbCA8LSBsYXBwbHkoY2RsLGZ1bmN0aW9uKGQpIGRbLGNvbFN1bXMoZCk+PW1pbi5zaXplLGRyb3A9Rl0pCgptZGwgPC0gbGFwcGx5KHBsLGZ1bmN0aW9uKGQpIHJlYWQuZGVsaW0ocGFzdGUoZCwnYW5ub3QuY3N2JyxzZXA9Jy8nKSxzZXA9JywnLGhlYWRlcj1ULHN0cmluZ3NBc0ZhY3RvcnMgPSBGKSkKa2lkbmV5LmNlbGwuZiA8LSBzZXROYW1lcyh1bmxpc3QobGFwcGx5KG1kbCxmdW5jdGlvbihkKSBkWywyXSkpLHVubGlzdChsYXBwbHkocGFnb2RhMjo6OnNuKG5hbWVzKG1kbCkpLGZ1bmN0aW9uKG4pIHBhc3RlKG4sbWRsW1tuXV1bLDFdLHNlcD0nXycpKSkpCmBgYAoKCkdldCBjZWxsIGN5Y2xlIGdlbmVzCmBgYHtyfQpjYy5nZW5lcyA8LSByZWFkUkRTKCJ+cGtoYXJjaGVua28vbS9uaW5pYi9OQi9jYy5nZW5lcy5yZHMiKQpgYGAKCkdlbmVyYXRlIHAyIGFwcHMsIG9taXR0aW5nIGNlbGwgY3ljbGUgZ2VuZXMgYW5kIHJlLWNhbGN1bGF0aW5nIHZhcmlhbmNlIG5vcm0KYGBge3J9CmNkbC5wMiA8LSBsYXBwbHkoY2RsLGZ1bmN0aW9uKGNkKSB7CiAgY2QgPC0gY2RbIXJvd25hbWVzKGNkKSVpbiVjYy5nZW5lc1ssMl0sIF0KICBiYXNpY1AycHJvYyhjZCxuLmNvcmVzPTMwLG1ha2UuZ2VuZWtubiA9IEZBTFNFLGdldC50c25lID0gVCxnZXQubGFyZ2V2aXMgPSBGQUxTRSkKfSkKYGBgCgoKYGBge3J9CiNjZGwucDIgPC0gbGFwcGx5KGNkbCxiYXNpY1AycHJvYyxuLmNvcmVzPTIwLG1pbi5jZWxscy5wZXIuZ2VuZT0tMSwgZ2V0LmxhcmdldmlzPUYsIGdldC50c25lPVQsIG1ha2UuZ2VuZWtubj1GLG4ub2RnZW5lcz0yZTMsIG5QY3M9NTApCmBgYAoKTG9hZCBhZHJlbmFsIGRhdGEKVGhpcyBjb250YWlucyBhZHJlbmFsIGRhdGEKYGBge3J9Cm5jZGNvbkEgPC0gcmVhZFJEUygifnBraGFyY2hlbmtvL20vbmluaWIvTkIvbmNkY29uQS5yZHMiKQpuZXd3X2Fubm90IDwtIHJlYWRSRFMoIn5wa2hhcmNoZW5rby9tL25pbmliL05CL25ld3dfYW5ub3QucmRzIikKYGBgCgpgYGB7cn0KYXAyIDwtIHJlYWRSRFMoIn5wa2hhcmNoZW5rby9tL25pbmliL05CL2ZpZ3VyZXMvZmlnMS9hcDIucDIucmRzIikKYGBgCgpSZWNhbGN1bGF0ZSB3aXRob3V0IGNlbGwgY3ljbGUgZ2VuZXMgYW5kIGNsdXN0ZXIgMTAKYGBge3J9CnggPC0gYXAyJG1pc2MkcmF3Q291bnRzCmYgPC0gYXAyJGNsdXN0ZXJzJFBDQVtbMV1dOwp4IDwtIHhbIXJvd25hbWVzKHgpICVpbiUgbmFtZXMoZilbZj09MTBdLCAhY29sbmFtZXMoeCkgJWluJSBjYy5nZW5lc1ssMl1dCmFwMmIgPC0gYmFzaWNQMnByb2ModCh4KSxuLmNvcmVzPTMwLG1ha2UuZ2VuZWtubiA9IEZBTFNFLGdldC50c25lID0gVCxnZXQubGFyZ2V2aXMgPSBGQUxTRSkKYGBgCgoKCmBgYHtyfQpjb25hIDwtIENvbm9zJG5ldyhjKGNkbC5wMixsaXN0KCJBZHIiPWFwMmIpKSkKY29uYSRidWlsZEdyYXBoKGs9MTUsay5zZWxmPTUsay5zZWxmLndlaWdoPTAuMSxuY29tcHM9MzAsbi5vZGdlbmVzPTJlMyxzcGFjZT0nUENBJykKY29uYSRmaW5kQ29tbXVuaXRpZXMobWV0aG9kPWxlaWRlbi5jb21tdW5pdHkscmVzb2x1dGlvbj0xKQpjb25hJGVtYmVkR3JhcGgobWV0aG9kPSdVTUFQJyxzZWVkPTEsc3ByZWFkPTEwKQpjb25hJG1pc2MkZW1iZWRkaW5ncyA8LSBsaXN0KCJVTUFQIj1jb25hJGVtYmVkZGluZykKYGBgCgoKCmBgYHtyIGZpZy5oZWlnaHQ9OC41LCBmaWcud2lkdGg9OC41fQpzaXplIDwtIDAuMTsgYWxwaGEgPC0gMC41Owp0aXNzdWVmIDwtIGNvbmEkZ2V0RGF0YXNldFBlckNlbGwoKTsKdGlzc3VlZiA8LSBhcy5mYWN0b3Ioc2V0TmFtZXMoaWZlbHNlKGdyZXBsKCdedycsbmFtZXModGlzc3VlZikpLCdraWRuZXknLCdhZHJlbmFsJyksbmFtZXModGlzc3VlZikpKQpwbCA8LSBsaXN0KGNvbmEkcGxvdEdyYXBoKGdyb3Vwcz10aXNzdWVmLCBzaXplPXNpemUsIGFscGhhPWFscGhhLHRpdGxlPSd0aXNzdWUnLG1hcmsuZ3JvdXBzPUYpLAogICAgICAgICAgIGNvbmEkcGxvdEdyYXBoKGdyb3Vwcz1hcDIkY2x1c3RlcnMkUENBW1sxXV0sc2l6ZT1zaXplLCBhbHBoYT1hbHBoYSx0aXRsZT0nYWRyZW5hbCBjbHVzdGVycycscGxvdC5uYT1GKSwKICAgICAgICAgICBjb25hJHBsb3RHcmFwaChncm91cHMgPSBraWRuZXkuY2VsbC5mLCBzaXplPXNpemUsIGFscGhhPWFscGhhLHRpdGxlPSdraWRuZXknLGZvbnQuc2l6ZT1jKDMsNSkscGxvdC5uYT1GKSwKICAgICAgICAgICBjb25hJHBsb3RHcmFwaChncm91cHMgPSBuZXd3X2Fubm90LCBzaXplPXNpemUsIGFscGhhPWFscGhhLHRpdGxlPSdhZHJlbmFsJyxwbG90Lm5hPUYpKQpwbG90X2dyaWQocGxvdGxpc3Q9cGwsbnJvdz0yKQpgYGAKCgpgYGB7ciBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD00fQpjb25hJHBsb3RHcmFwaChncm91cHM9c2V0TmFtZXMoYXAyJGNsdXN0ZXJzJFBDQVtbMV1dPT03LG5hbWVzKGFwMiRjbHVzdGVycyRQQ0FbWzFdXSkpLHNpemU9c2l6ZSwgYWxwaGE9YWxwaGEsdGl0bGU9J2FkcmVuYWwgY2x1c3RlcnMnLHBsb3QubmE9RikKYGBgCgoKYGBge3J9CmFubm90IDwtIHJlYWRSRFMoIi9kMC1tZW5kZWwvaG9tZS9tZWlzbC9Xb3JrcGxhY2UvbmV1cm9ibGFzdG9tYS9jZWxsLmFubm90YXRpb24uSmFuMjAyMC5yZHMiKSRjZWxsYW5vCmFubm90IDwtIHNldE5hbWVzKGFzLmNoYXJhY3Rlcihhbm5vdCksbmFtZXMoYW5ub3QpKQphbm5vdFthbm5vdD09J3Vua25vd24nXSA8LSAnRXJ5dGhyb2lkJwphbm5vdFthbm5vdD09J1BsYXNtYWN5dG9pZCddIDwtICdwREMnCmFubm90W2dyZXAoJ0N5dG90b3hpYycsYW5ub3QpXSA8LSAnVGN5dG8nCmFubm90W2dyZXAoJ1QgaGVscGVyJyxhbm5vdCldIDwtICdUaCcKYW5ub3RbZ3JlcCgnTksnLGFubm90KV0gPC0gJ05LJwphbm5vdFtncmVwKCdCY2VsbCcsYW5ub3QpXSA8LSAnQicKYW5ub3RbZ3JlcCgnUGxhc21hQ2VsbCcsYW5ub3QpXSA8LSAnUGxhc21hJwphbm5vdFtncmVwKCdNYXN0Jyxhbm5vdCldIDwtICdNYXN0Jwphbm5vdFtncmVwKCJCcmlkZ2UiLGFubm90KV0gPC0gJ1NDUC1saWtlJwpkYW5ub3QgPC0gYXMuZmFjdG9yKGFubm90KQoKYW5ub3RbZ3JlcCgiU09YMTF8U3RyZXNzfGV1cm9uYWx8UHJvbGlmIixhbm5vdCldIDwtICdOb3JhZHJlbmVyZ2ljJwphbm5vdFtncmVwKCJCcmlkZ2UiLGFubm90KV0gPC0gJ1NDUC1saWtlJwphbm5vdCA8LSBhcy5mYWN0b3IoYW5ub3QpOwojIHJlbmFtaW5ncwpsZXZlbHMoYW5ub3QpIDwtIGdzdWIoIk5vcmFkcmVuZXJnaWMiLCJBZHJlbmVyZ2ljIixsZXZlbHMoYW5ub3QpKQpgYGAKCkxvYWQgYW5ub3RhdGlvbnMgYW5kIHBhbGV0dGVzOgpgYGB7cn0KbG9hZCgiLi4vZmlndXJlcy9maWcxL2Fubm90YXRpb25zLlJEYXRhIikKYGBgCgoKCkFsaWduIGV2ZXJ5dGhpbmcgdG9nZXRoZXI6CgpgYGB7cn0KY29uYiA8LSBDb25vcyRuZXcoYyhjZGwucDIsbmNkY29uQSRzYW1wbGVzW2dyZXBsKCJeTkIiLG5hbWVzKG5jZGNvbkEkc2FtcGxlcykpXSxsaXN0KCJBZHIiPWFwMmIpKSkKYGBgCgoKYGBge3J9CmNvbmIkYnVpbGRHcmFwaChrPTE1LGsuc2VsZj0yMCxrLnNlbGYud2VpZ2g9MC41LG5jb21wcz0zMCxuLm9kZ2VuZXM9MmUzLHNwYWNlPSdQQ0EnKQpjb25iJGZpbmRDb21tdW5pdGllcyhtZXRob2Q9bGVpZGVuLmNvbW11bml0eSxyZXNvbHV0aW9uPTEpCmNvbmIkZW1iZWRHcmFwaChtZXRob2Q9J1VNQVAnLHNlZWQ9MCwgbWluLmRpc3Q9MC4wMSwgc3ByZWFkPTEwLCBtaW4ucHJvYi5sb3dlcj0xZS0zLG4uZXBvY2hzPTJlMykKY29uYiRtaXNjJGVtYmVkZGluZ3MgPC0gbGlzdCgiVU1BUCI9Y29uYiRlbWJlZGRpbmcpCmBgYAoKCmBgYHtyfQpjb25iJG1pc2MkZW1iZWRkaW5ncyRsYXJnZVZpcyA8LSBjb25iJGVtYmVkR3JhcGgobWV0aG9kPSdsYXJnZVZpcycsYWxwaGE9Mywgc2dkX2JhdGNoZXMgPSAzZTgpCmBgYAoKYGBge3J9CmNvbmIkbWlzYyRlbWJlZGRpbmdzJFVNQVAyIDwtIGNvbmIkZW1iZWRHcmFwaChtZXRob2Q9J1VNQVAnLCBtaW4uZGlzdD0wLjAxLCBzcHJlYWQ9MTUsIG1pbi5wcm9iLmxvd2VyPTFlLTMsbi5lcG9jaHM9MmUzKQpgYGAKCgoKYGBge3IgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTEwfQpzaXplIDwtIDAuMTsgYWxwaGEgPC0gMC4xOwp0aXNzdWVmIDwtIGNvbmIkZ2V0RGF0YXNldFBlckNlbGwoKTsKdGlzc3VlZiA8LSBzZXROYW1lcyhhcy5jaGFyYWN0ZXIodGlzc3VlZiksbmFtZXModGlzc3VlZikpCnRpc3N1ZWZbZ3JlcCgiXnciLHRpc3N1ZWYpXSA8LSAna2lkbmV5Jwp0aXNzdWVmW2dyZXAoIl5BZHIiLHRpc3N1ZWYpXSA8LSAnYWRyZW5hbCcKdGlzc3VlZltncmVwKCJeTkIiLHRpc3N1ZWYpXSA8LSAnTkInCgpwbCA8LSBsaXN0KGNvbmIkcGxvdEdyYXBoKGdyb3Vwcz10aXNzdWVmLCBzaXplPXNpemUsIGFscGhhPWFscGhhLHRpdGxlPSd0aXNzdWUnLG1hcmsuZ3JvdXBzPUYpLAogICAgICAgICAgIGNvbmIkcGxvdEdyYXBoKGdyb3Vwcz1hbm5vdCxzaXplPXNpemUsIGFscGhhPWFscGhhLHRpdGxlPSduZXVyb2JsYXN0b21hJyxwbG90Lm5hPUYpLAogICAgICAgICAgIGNvbmIkcGxvdEdyYXBoKGdyb3VwcyA9IGtpZG5leS5jZWxsLmYsIHNpemU9c2l6ZSwgYWxwaGE9YWxwaGEsdGl0bGU9J2tpZG5leScsZm9udC5zaXplPWMoMyw1KSxwbG90Lm5hPUYpLAogICAgICAgICAgI2NvbmIkcGxvdEdyYXBoKGdyb3VwcyA9IG5ld3dfYW5ub3QsIHNpemU9c2l6ZSwgYWxwaGE9YWxwaGEsdGl0bGU9J2FkcmVuYWwnLHBsb3QubmE9RikpCiAgICAgICAgICBjb25iJHBsb3RHcmFwaChncm91cHMgPSBhcDIkY2x1c3RlcnMkUENBW1sxXV0sIHNpemU9c2l6ZSo0LCBhbHBoYT1hbHBoYSx0aXRsZT0nYWRyZW5hbCcscGxvdC5uYT1GKSkKCnBsb3RfZ3JpZChwbG90bGlzdD1wbCxucm93PTIpCmBgYAoKYGBge3J9Cm5pbmliX2Jhbm5vdCA8LSByZWFkUkRTKCJ+cGtoYXJjaGVua28vbS9uaW5pYi9OQi9uaW5pYl9iYW5ub3QucmRzIikKYGBgCgpVcGRhdGUgYWRyZW5hbCBhbm5vdGF0aW9uCmBgYHtyfQphZHIuYW5uIDwtIHNldE5hbWVzKGFzLmNoYXJhY3RlcihuZXd3X2Fubm90KSxuYW1lcyhuZXd3X2Fubm90KSk7CmFkci5hbm5bYWRyLmFubj09J0ltbXVuZSBjZWxscyddIDwtICdLaWRuZXkgY2VsbHMnCmFkci5hbm5bYWRyLmFubj09J1Zlc3NlbHMnXSA8LSAnRW5kb3RoZWxpYWwnCmFkci5hbm5bYWRyLmFubj09J0Jsb29kIGNlbGxzJ10gPC0gJ0VyeXRocm9pZCcKYWRyLmFublthZHIuYW5uPT0nUGVyaWN5dGVzJ10gPC0gJ01lc2VuY2h5bWFsJwphZHIuYW5uW2Fkci5hbm49PSdTeW1wYXRvYmxhc3RzJ10gPC0gJ1N5bXBhdGhvYmxhc3RzJwphZHIuYW5uW2Fkci5hbm49PSdTY2h3YW5uIGNlbGxzJ10gPC0gJ1NDUHMnCmNsIDwtIGFwMiRjbHVzdGVycyRQQ0FbWzFdXQphZHIuYW5uW25hbWVzKGFkci5hbm4pICVpbiUgbmFtZXMoY2wpW2NsPT0nMTgnXV0gPC0gIk15ZWxvaWQiCmFkci5hbm5bbmFtZXMoYWRyLmFubikgJWluJSBuYW1lcyhjbClbY2w9PScxMSddXSA8LSAiTHltcGhvaWQiCmFkci5hbm5bbmFtZXMoYWRyLmFubikgJWluJSBuYW1lcyhjbClbY2w9PScxNCddXSA8LSAiS2lkbmV5IGNlbGxzIgphZHIuYW5uIDwtIGFzLmZhY3RvcihhZHIuYW5uKQoKc2V0LnNlZWQoNCkKYWRyLnBhbCA8LSBzYW1wbGUocmFpbmJvdyhsZW5ndGgobGV2ZWxzKGFubm90KSksdj0wLjk1LHM9MC44NSkpOwphZHIucGFsZiA8LSBmdW5jdGlvbihuKSByZXR1cm4oYWRyLnBhbCkKYGBgCgpXaXRoIGRlbnNpdHkgY29udG91cgpgYGB7cn0KbGluZXR5cGUgPC0gMTsgY29sb3I9J2dyZXkyMCc7CmtkIDwtIGtzOjprZGUoY29uYiRlbWJlZGRpbmdbcm93bmFtZXMoY29uYiRlbWJlZGRpbmcpICVpbiUgbmFtZXMoYW5ub3QpW2Fubm90PT0nTm9yYWRyZW5lcmdpYyddLF0sIGNvbXB1dGUuY29udD1UUlVFKQpjbiA8LSB3aXRoKGtkLCBjb250b3VyTGluZXMoeD1ldmFsLnBvaW50c1tbMV1dLCB5PWV2YWwucG9pbnRzW1syXV0sej1lc3RpbWF0ZSwgbGV2ZWxzPWNvbnRbIjEwJSJdKVtbMV1dKQpjbiA8LSBnZW9tX3BhdGgoYWVzKHgsIHkpLCBkYXRhPWRhdGEuZnJhbWUoY24pLGxpbmV0eXBlID0gbGluZXR5cGUgLCBjb2xvcj1jb2xvcik7CgprZCA8LSBrczo6a2RlKGNvbmIkZW1iZWRkaW5nW3Jvd25hbWVzKGNvbmIkZW1iZWRkaW5nKSAlaW4lIG5hbWVzKGFubm90KVthbm5vdD09J01lc2VuY2h5bWFsJ10sXSwgY29tcHV0ZS5jb250PVRSVUUpCmNtIDwtIHdpdGgoa2QsIGNvbnRvdXJMaW5lcyh4PWV2YWwucG9pbnRzW1sxXV0sIHk9ZXZhbC5wb2ludHNbWzJdXSx6PWVzdGltYXRlLCBsZXZlbHM9Y29udFsiMTAlIl0pW1sxXV0pCmNtIDwtIGdlb21fcGF0aChhZXMoeCwgeSksIGRhdGE9ZGF0YS5mcmFtZShjbSksbGluZXR5cGUgPSBsaW5ldHlwZSAsIGNvbG9yPWNvbG9yKTsKCmtkIDwtIGtzOjprZGUoY29uYiRlbWJlZGRpbmdbcm93bmFtZXMoY29uYiRlbWJlZGRpbmcpICVpbiUgbmFtZXMoYW5ub3QpW2Fubm90PT0nU0NQLWxpa2UnXSxdLCBjb21wdXRlLmNvbnQ9VFJVRSwgYXBwcm94LmNvbnQgPSBGKQoja2QgPC0ga3M6OmtkZShjb25iJGVtYmVkZGluZ1tyb3duYW1lcyhjb25iJGVtYmVkZGluZykgJWluJSBuYW1lcyhuaW5pYl9iYW5ub3QpW25pbmliX2Jhbm5vdD09J0ZpYnJvYmxhc3RfQnJpZGdlJ10sXSwgY29tcHV0ZS5jb250PVRSVUUpCmNiIDwtIHdpdGgoa2QsIGNvbnRvdXJMaW5lcyh4PWV2YWwucG9pbnRzW1sxXV0sIHk9ZXZhbC5wb2ludHNbWzJdXSx6PWVzdGltYXRlLCBsZXZlbHM9Y29udFsiNTAlIl0pW1sxXV0pCmNiIDwtIGdlb21fcGF0aChhZXMoeCwgeSksIGRhdGE9ZGF0YS5mcmFtZShjYiksbGluZXR5cGUgPSBsaW5ldHlwZSAsIGNvbG9yPWNvbG9yKTsKCmBgYAoKCgoKYGBge3IgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTEwfQpzaXplIDwtIDAuMTsgYWxwaGEgPC0gMC4xOwp0aXNzdWVmIDwtIGNvbmIkZ2V0RGF0YXNldFBlckNlbGwoKTsKdGlzc3VlZiA8LSBzZXROYW1lcyhhcy5jaGFyYWN0ZXIodGlzc3VlZiksbmFtZXModGlzc3VlZikpCnRpc3N1ZWZbZ3JlcCgiXnciLHRpc3N1ZWYpXSA8LSAna2lkbmV5Jwp0aXNzdWVmW2dyZXAoIl5BZHIiLHRpc3N1ZWYpXSA8LSAnYWRyZW5hbCcKdGlzc3VlZltncmVwKCJeTkIiLHRpc3N1ZWYpXSA8LSAnTkInCnJhc3RlciA8LSBUOwoKI2FmIDwtIHNldE5hbWVzKG5hbWVzKHRpc3N1ZWYpICVpbiUgbmFtZXMobmluaWJfYmFubm90KVtuaW5pYl9iYW5ub3Q9PSdGaWJyb2JsYXN0X0JyaWRnZSddLG5hbWVzKHRpc3N1ZWYpKQoKcGwgPC0gbGlzdChjb25iJHBsb3RHcmFwaChncm91cHM9dGlzc3VlZiwgc2l6ZT1zaXplLCBhbHBoYT1hbHBoYSxyYXN0ZXI9cmFzdGVyLCB0aXRsZT0ndGlzc3VlJyxtYXJrLmdyb3Vwcz1GKSsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPWMoMC44NSwgMC4xNSkpICsgZ3VpZGVzKGNvbG9yPWd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9MyxhbHBoYT0wLjgpLHRpdGxlPSdUaXNzdWU6JykpLAogICAgICAgICAgIGNvbmIkcGxvdEdyYXBoKGdyb3Vwcz1hbm5vdCxzaXplPXNpemUsIGFscGhhPWFscGhhLHJhc3Rlcj1yYXN0ZXIsdGl0bGU9J25ldXJvYmxhc3RvbWEnLHBsb3QubmE9RixwYWxldHRlPWFubm90LnBhbCksCiAgICAgICAgICAgY29uYiRwbG90R3JhcGgoZ3JvdXBzID0ga2lkbmV5LmNlbGwuZiwgc2l6ZT1zaXplLCBhbHBoYT1hbHBoYSxyYXN0ZXI9cmFzdGVyLHRpdGxlPSdraWRuZXknLGZvbnQuc2l6ZT1jKDMsNSkscGxvdC5uYT1GKSwKICAgICAgICAgIGNvbmIkcGxvdEdyYXBoKGdyb3VwcyA9IGFkci5hbm4sIHNpemU9c2l6ZSo0LCBhbHBoYT1hbHBoYSoyLHJhc3Rlcj1yYXN0ZXIsdGl0bGU9J2FkcmVuYWwnLHBsb3QubmE9RixwYWxldHRlPWFkci5wYWxmLGZvbnQuc2l6ZT1jKDQsNSkpKQogICAgICAgICAgI2NvbmIkcGxvdEdyYXBoKGdyb3VwcyA9IGFwMiRjbHVzdGVycyRQQ0FbWzFdXSwgc2l6ZT1zaXplKjQsIGFscGhhPWFscGhhKjIsdGl0bGU9J2FkcmVuYWwnLHBsb3QubmE9RikpCgpwbCA8LSBsYXBwbHkocGwsZnVuY3Rpb24ocCkgcCtjbitjbStjYisgeGxpbShyYW5nZShjb25iJGVtYmVkZGluZ1ssMV0pKSt5bGltKHJhbmdlKGNvbmIkZW1iZWRkaW5nWywyXSkpKQoKcHAgPC0gcGxvdF9ncmlkKHBsb3RsaXN0PXBsLG5yb3c9MikKCnBwCmBgYAoKYGBge3J9CnBkZignY29tYmluZWQucGRmJyx3aWR0aD0xMCxoZWlnaD0xMCk7IHByaW50KHBwKTsgZGV2Lm9mZigpOwppbnZpc2libGUobGFwcGx5KDE6bGVuZ3RoKHBsKSxmdW5jdGlvbihpKSB7IHBkZihwYXN0ZSgncGFuZWwnLGksJ3BkZicsc2VwPScuJyksd2lkdGg9NCxoZWlnaHQ9NCk7IHByaW50KHBsW1tpXV0pOyBkZXYub2ZmKCl9KSkKYGBgCgoKUmVzdHJpY3RlZCB3aW5kb3cgcGxvdCBmb3IgdGhlIG1haW4gZmlndXJlOgoKYGBge3IgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9My41fQpzaXplIDwtIDAuMzsgYWxwaGEgPC0gMC4xOwpyYXN0ZXIgPC0gVDsKeGxpbSA8LSBjKC02Niw0MCk7IHlsaW0gPC0gYygtNjMsMCkKCiNhZiA8LSBzZXROYW1lcyhuYW1lcyh0aXNzdWVmKSAlaW4lIG5hbWVzKG5pbmliX2Jhbm5vdClbbmluaWJfYmFubm90PT0nRmlicm9ibGFzdF9CcmlkZ2UnXSxuYW1lcyh0aXNzdWVmKSkKCnBsIDwtIGxpc3QoY29uYiRwbG90R3JhcGgoZ3JvdXBzPXRpc3N1ZWYsIHNpemU9c2l6ZSwgYWxwaGE9YWxwaGEscmFzdGVyPXJhc3RlciwgcmFzdGVyLndpZHRoPTYuNixyYXN0ZXIuaGVpZ2h0PTQsIHRpdGxlPSd0aXNzdWUnLG1hcmsuZ3JvdXBzPUYpLAogICAgICAgICAgIGNvbmIkcGxvdEdyYXBoKGdyb3Vwcz1hbm5vdCxzaXplPXNpemUsIGFscGhhPWFscGhhLHJhc3Rlcj1yYXN0ZXIsdGl0bGU9J25ldXJvYmxhc3RvbWEnLCByYXN0ZXIud2lkdGg9Ni42LHJhc3Rlci5oZWlnaHQ9NCxwbG90Lm5hPUYscGFsZXR0ZT1hbm5vdC5wYWwpLAogICAgICAgICAgICNjb25iJHBsb3RHcmFwaChncm91cHMgPSBraWRuZXkuY2VsbC5mLCBzaXplPXNpemUsIGFscGhhPWFscGhhLHJhc3Rlcj1yYXN0ZXIsdGl0bGU9J2tpZG5leScsZm9udC5zaXplPWMoMyw1KSxwbG90Lm5hPUYpLAogICAgICAgICAgY29uYiRwbG90R3JhcGgoZ3JvdXBzID0gYWRyLmFubiwgc2l6ZT1zaXplKjQsIGFscGhhPWFscGhhKjIscmFzdGVyPXJhc3Rlcix0aXRsZT0nYWRyZW5hbCcsIHJhc3Rlci53aWR0aD02LjYscmFzdGVyLmhlaWdodD00LHBsb3QubmE9RixwYWxldHRlPWFkci5wYWxmLGZvbnQuc2l6ZT1jKDQsNSkpKQogICAgICAgICAgI2NvbmIkcGxvdEdyYXBoKGdyb3VwcyA9IGFwMiRjbHVzdGVycyRQQ0FbWzFdXSwgc2l6ZT1zaXplKjQsIGFscGhhPWFscGhhKjIsdGl0bGU9J2FkcmVuYWwnLHBsb3QubmE9RikpCgojcGwgPC0gbGFwcGx5KHBsLGZ1bmN0aW9uKHApIHArY24rY20rY2IrIGNvb3JkX2NhcnRlc2lhbih4bGltLHlsaW0sY2xpcD0ib24iKSkKIyBwbCA8LSBsYXBwbHkocGwsZnVuY3Rpb24ocCkgcCtjbitjbStjYitzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSkgKwojICAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpICsgeGxpbSh4bGltKSt5bGltKHlsaW0pCiMgICApCgpwbCA8LSBsYXBwbHkocGwsZnVuY3Rpb24ocCkgcCtjbitjbStjYitjb29yZF9jYXJ0ZXNpYW4oZXhwYW5kID0gYygwLCAwKSwgeGxpbT14bGltLHlsaW09eWxpbSkgK3hsaW0oeGxpbSkreWxpbSh5bGltKSkKCgpwcCA8LSBwbG90X2dyaWQocGxvdGxpc3Q9cGwsbmNvbD0xKQpwZGYoJ2NvbWJpbmVkX25hcnJvdy5wZGYnLHdpZHRoPTMuNSxoZWlnaD02KTsgcHJpbnQocHApOyBkZXYub2ZmKCk7CmludmlzaWJsZShsYXBwbHkoMTpsZW5ndGgocGwpLGZ1bmN0aW9uKGkpIHsgcGRmKHBhc3RlKCdwYW5lbCcsaSwnbmFycm93JywncGRmJyxzZXA9Jy4nKSx3aWR0aD0zLjUsaGVpZ2h0PTIpOyBwcmludChwbFtbaV1dKTsgZGV2Lm9mZigpfSkpCnBwCmBgYAoKCmBgYHtyIGZpZy5oZWlnaHQ9NCxmaWcud2lkdGg9NH0KY29uYiRwbG90R3JhcGgoZ2VuZT0nTlI1QTEnLGFscGhhPTAuNCxzaXplPTAuMSkKYGBgCgpgYGB7cn0KamdycCA8LSBjb25iJGZpbmRDb21tdW5pdGllcyhtZXRob2Q9bGVpZGVuLmNvbW11bml0eSxyPTQpJGdyb3VwcwpgYGAKCmBgYHtyIGZpZy5oZWlnaHQ9NixmaWcud2lkdGg9Nn0KY29uYiRwbG90R3JhcGgoYWxwaGE9MC4xLHNpemU9MC4xLG1hcmsuZ3JvdXBzPVQsZ3JvdXBzPWpncnApCmBgYAoKCklsbHVzdHJhdGUgc2ltaWxhcml0eSBiZXR3ZWVuIGJyaWRnZSBhbmQgU0NQczoKCklkZW50aWZ5IGdlbmVzIHRoYXQgc2V0IGJvdGggcG9wdWxhdGlvbnMgYXBhcnQgZnJvbSBvdGhlcnM6CmBgYHtyfQpmYWMgPC0gY29uYiRnZXREYXRhc2V0UGVyQ2VsbCgpCmZhYyA8LSBzZXROYW1lcyhyZXAoJ290aGVyJyxsZW5ndGgoZmFjKSksbmFtZXMoZmFjKSkKI2ZhY1tuYW1lcyhmYWMpICVpbiUgYyhuYW1lcyhhZHIuYW5uKVthZHIuYW5uPT0nU2Nod2FubiBjZWxscyddLCBuYW1lcyhhbm5vdClbYW5ub3Q9PSdTQ1AtbGlrZSddKV0gPC0gJ1NDUHMnCnNjaHdhbm4uZGUgPC0gY29uYiRnZXREaWZmZXJlbnRpYWxHZW5lcyhncm91cHM9ZmFjLG4uY29yZXM9MzAsYXBwZW5kLmF1Yz1UUlVFLHoudGhyZXNob2xkPTAsdXByZWd1bGF0ZWQub25seT1UKQpgYGAKCmBgYHtyIGZpZy53aWR0aD01LGZpZy5oZWlnaHQ9NX0Kc291cmNlKCJ+L20vcDIvY29ub3MvUi9wbG90LlIiKQpzZmFjIDwtIGZhYzsgc2ZhYyA8LSBzZmFjW3VubGlzdCh0YXBwbHkoMTpsZW5ndGgoZmFjKSxmYWMsc2FtcGxlLG1pbih0YWJsZShmYWMpKSkpXQpwcCA8LSBwbG90REVoZWF0bWFwKGNvbmIsc2ZhYyxzY2h3YW5uLmRlLG4uZ2VuZXMucGVyLmNsdXN0ZXIgPSAzMCAsc2hvdy5nZW5lLmNsdXN0ZXJzPVQscm93LmxhYmVsLmZvbnQuc2l6ZSA9IDYsY29sdW1uLm1ldGFkYXRhID0gbGlzdCh0aXNzdWU9dGlzc3VlZikpCiNwZGYoZmlsZT0nYWRyLmhlYXRtYXAucGRmJyx3aWR0aD03LGhlaWdodD0xNSk7IHByaW50KHBwKTsgZGV2Lm9mZigpOwpwcApgYGAKCmBgYHtyfQpmYWMyIDwtIGNvbmIkZ2V0RGF0YXNldFBlckNlbGwoKQpmYWMyIDwtIHNldE5hbWVzKHJlcCgnb3RoZXInLGxlbmd0aChmYWMyKSksbmFtZXMoZmFjMikpCiNmYWNbbmFtZXMoZmFjKSAlaW4lIGMobmFtZXMoYWRyLmFubilbYWRyLmFubj09J1NjaHdhbm4gY2VsbHMnXSwgbmFtZXMoYW5ub3QpW2Fubm90PT0nU0NQLWxpa2UnXSldIDwtICdTQ1BzJwpmYWMyW25hbWVzKGZhYzIpICVpbiUgYyhuYW1lcyhqZ3JwKVtqZ3JwPT0nMzcnXSldIDwtICdTQ1BzJwpzY2h3YW5uLmRlMiA8LSBjb25iJGdldERpZmZlcmVudGlhbEdlbmVzKGdyb3Vwcz1mYWMyLG4uY29yZXM9MzAsYXBwZW5kLmF1Yz1UUlVFLHoudGhyZXNob2xkPTAsdXByZWd1bGF0ZWQub25seT1UKQpgYGAKCmBgYHtyIGZpZy53aWR0aD0xNCxmaWcuaGVpZ2h0PTd9CnNvdXJjZSgifi9tL3AyL2Nvbm9zL1IvcGxvdC5SIikKc2ZhYyA8LSBhcy5mYWN0b3IoZmFjMik7IHNmYWMgPC0gZHJvcGxldmVscyhzZmFjW3VubGlzdCh0YXBwbHkoMTpsZW5ndGgoZmFjKSxmYWMsc2FtcGxlLG1pbih0YWJsZShmYWMpKSkpXSkKcHAgPC0gcGxvdERFaGVhdG1hcChjb25iLHNmYWMsc2Nod2Fubi5kZTIsbi5nZW5lcy5wZXIuY2x1c3RlciA9IDMwICxzaG93LmdlbmUuY2x1c3RlcnM9VCxyb3cubGFiZWwuZm9udC5zaXplID0gNyxjb2x1bW4ubWV0YWRhdGEgPSBsaXN0KHRpc3N1ZT10aXNzdWVmKSkKI3BkZihmaWxlPSdhZHIuaGVhdG1hcC5wZGYnLHdpZHRoPTcsaGVpZ2h0PTE1KTsgcHJpbnQocHApOyBkZXYub2ZmKCk7CnBwCmBgYAoKVGhlcmUncyBhIGdyb3VwIHdpdGhpbiB0aGUgJ290aGVyJyB0aGF0IHNlZW1zIHRvIHNoYXJlIHRoZSBTQ1AgcGF0dGVybiAuLi4KYGBge3IgZmlnLmhlaWdodD00LGZpZy53aWR0aD00fQpjb25iJHBsb3RHcmFwaChnZW5lPSdQTFAxJyxhbHBoYT0wLjQsc2l6ZT0wLjEpCmBgYAoKYGBge3IgZmlnLmhlaWdodD00LGZpZy53aWR0aD00fQpjb25iJHBsb3RHcmFwaChnZW5lPSdTMTAwQicsYWxwaGE9MC40LHNpemU9MC4xKQpgYGAKCmBgYHtyfQpxcGxvdCA8LSBmdW5jdGlvbihnLCBjb24ub2JqLCBhbm4pIHsKICAjY2F0KGcsJyAnKQogIHggPC0gbGFwcGx5KGNvbi5vYmokc2FtcGxlc1s1OjEwXSxmdW5jdGlvbihyKSB7IGlmKGclaW4lIGNvbG5hbWVzKHIkY291bnRzKSkgeyByJGNvdW50c1ssZ10gfSBlbHNlIHsgcmV0dXJuKE5VTEwpIH0gfSkKICBpZihsZW5ndGgodW5saXN0KHgpKTwxKSBzdG9wKCdnZW5lICcsZywnIGlzIG5vdCBmb3VuZCcpCiAgZGYgPC0gZGF0YS5mcmFtZSh2YWw9dW5saXN0KHgpLGNlbGw9dW5saXN0KGxhcHBseSh4LG5hbWVzKSkpCiAgZGYkY2x1c3RlciA8LSBhbm5bbWF0Y2goZGYkY2VsbCxuYW1lcyhhbm4pKV0KICBkZiA8LSBuYS5vbWl0KGRmKQogIAogIG12IDwtIG1heCh0YXBwbHkoZGYkdmFsLGRmJGNsdXN0ZXIscXVhbnRpbGUscD0wLjgpLHRhcHBseShkZiR2YWwsZGYkY2x1c3RlcixtZWFuKSkqMS41CiAgcCA8LSBnZ3Bsb3QoZGYsYWVzKHg9Y2x1c3Rlcix5PXZhbCxjb2xvcj1jbHVzdGVyKSkrZ2VvbV9ib3hwbG90KG91dGxpZXIuc2hhcGUgPSBOQSkrIHN0YXRfc3VtbWFyeShmdW4uZGF0YT1tZWFuX3NlLGdlb209InBvaW50cmFuZ2UiLCBjb2xvcj0iYmxhY2siKSt5bGFiKGcpK2dndGl0bGUoZykrZ3VpZGVzKGNvbG91cj1GQUxTRSkrIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSx2anVzdD0wLjUpKStjb29yZF9jYXJ0ZXNpYW4oeWxpbT1jKDAsbXYpKTsKICBwCn0KYGBgCgoKYGBge3J9Cm1hcmtlcnMgPC0gYyhhcy5jaGFyYWN0ZXIoc2Nod2Fubi5kZTIkU0NQcyAlPiUgYXJyYW5nZSgtQVVDKSAlPiUgaGVhZCg4KSAlPiUgJyQnKCdHZW5lJykpLCdTT1gxMCcsJ01JQScpCmBgYAoKYGBge3IgZmlnLndpZHRoPTE0LGZpZy5oZWlnaHQ9MjB9CnBsIDwtIGxhcHBseShtYXJrZXJzLHFwbG90LGNvbmIsamdycCkKcGxvdF9ncmlkKHBsb3RsaXN0PXBsLG5jb2w9MikKYGBgCgpJdCdzIGNsdXN0ZXIgMzUsIHdoaWNoIGlzIHRoZSBleHRlbnNpb24gb2YgdGhlIGJyaWRnZSBpbnRvIHRoZSBmaWJyb2JsYXN0cyAuLi4gbW9zdCBsaWtlbHkgYXMgYSByZXN1bHQgb2YgY2x1c3RlcmluZyB1bmNlcnRhaW50eS4gClRoZSBjbHVzdGVyIHNob3VsZCBiZSBiZXR0ZXIgZGVmaW5lZCBvbiBOQiBhbG9uZSAuLi4KCkNoZWNrIGV4cHJlc3Npb24gb24gdGhlIE5CIGVtYmVkZGluZwpgYGB7cn0KbmNkY29uIDwtIHJlYWRSRFMoIn5wa2hhcmNoZW5rby9tL25pbmliL05CL25jZGNvbi5yZHMiKQpgYGAKCgpgYGB7ciBmaWcud2lkdGg9MTUsZmlnLmhlaWdodD01fQpwMSA8LSBuY2Rjb24kcGxvdEdyYXBoKGFscGhhPTAuMDMsc2l6ZT0wLjUsZ3JvdXBzPW5jZGNvbiRjbHVzdGVycyRybGVpZGVuJGdyb3VwcyxwbG90Lm5hPUYsbWFyay5ncm91cHM9VCkKcDIgPC0gbmNkY29uJHBsb3RHcmFwaChhbHBoYT0wLjAzLHNpemU9MC41LHBsb3QubmE9RixtYXJrLmdyb3Vwcz1ULGdyb3Vwcz1mYWMpCnAzIDwtIG5jZGNvbiRwbG90R3JhcGgoYWxwaGE9MC4wMyxzaXplPTAuNSxncm91cHM9ZmFjMixwbG90Lm5hPUYsbWFyay5ncm91cHM9VCkKcGxvdF9ncmlkKHBsb3RsaXN0PWxpc3QocDEscDIscDMpLG5yb3c9MSkKYGBgCgoKYGBge3IgZmlnLndpZHRoPTE0LGZpZy5oZWlnaHQ9MjB9CnBsIDwtIGxhcHBseShtYXJrZXJzLHFwbG90LG5jZGNvbixuY2Rjb24kY2x1c3RlcnMkcmxlaWRlbiRncm91cHMpCnBsb3RfZ3JpZChwbG90bGlzdD1wbCxuY29sPTIpCmBgYAoKSGVyZSB0bywgd2hpbGUgY2x1c3RlciAxLTUgY2FwdHVyZXMgbW9zdCBvZiB0aGUgU0NQcywgcGFydCBvZiB0aGUgc2lnbmF0dXJlIGlzIHN0aWxsIG5vdGFibGUgaW4gdGhlIGNsdXN0ZXIgNS0xIHRoYXQgY29ubmVjdHMgdG8gdGhlIG1lc3NlbmNoeW1hbCB0dW1vciBjZWxscwoKCkFsc28sIGluIGRvaW5nIERFIG9uIGZhYzIsIHRoZSBhZHJlbmFsIGFuZCBOQiBjb250cmlidXRpb25zIHRvIHRoZSBTQ1AgcG9wdWxhdGlvbiBhcmUgdmVyeSB1bmJhbGFuY2VkLiBJdCdzIHByb2JhYmx5IGJlc3QgdG8gcnVuIHRoZSB0d28gc2VwYXJhdGVseSBhbmQgaW50ZXJzZWN0IHRoZSBoaWdoLUFVQyBnZW5lczoKCmBgYHtyfQojIGZhY3RvciBmb3IganVzdCB0aGUgYWRyZW5hbCBTQ1AgY2VsbHMKZmFjMy5hZHIgPC0gY29uYiRnZXREYXRhc2V0UGVyQ2VsbCgpCmZhYzMuYWRyIDwtIHNldE5hbWVzKHJlcCgnb3RoZXInLGxlbmd0aChmYWMzLmFkcikpLG5hbWVzKGZhYzMuYWRyKSkKIyByZW1vdmUgTkIgU0NQLWxpa2UgY2VsbHMKZmFjMy5hZHIgPC0gZmFjMy5hZHJbIW5hbWVzKGZhYzMuYWRyKSAlaW4lIGMobmFtZXMoamdycClbamdycD09JzM1J10sIG5hbWVzKGFubm90KVthbm5vdD09J1NDUC1saWtlJ10sIGludGVyc2VjdChuYW1lcyhhbm5vdCksbmFtZXMoamdycClbamdycD09JzM3J10pKV0KZmFjMy5hZHJbbmFtZXMoZmFjMy5hZHIpICVpbiUgYyhuYW1lcyhhZHIuYW5uKVthZHIuYW5uPT0nU2Nod2FubiBjZWxscyddKV0gPC0gJ2FkcmVuYWwgU0NQJwojZmFjMy5hZHJbbmFtZXMoZmFjMy5hZHIpICVpbiUgYyhuYW1lcyhqZ3JwKVtqZ3JwPT0nMzcnXSldIDwtICdTQ1BzJyAjIHVzZSBhIG1vcmUgcmVzdHJpY3RpdmUgY2x1c3Rlci1iYXNlZCBkZWZpbml0aW9uCmZhYzMuYWRyIDwtIGRyb3BsZXZlbHMoYXMuZmFjdG9yKGZhYzMuYWRyKSkKdGFibGUoZmFjMy5hZHIsY29uYiRnZXREYXRhc2V0UGVyQ2VsbCgpW25hbWVzKGZhYzMuYWRyKV0pCnNjaHdhbm4uZGUzLmFkciA8LSBjb25iJGdldERpZmZlcmVudGlhbEdlbmVzKGdyb3Vwcz1mYWMzLmFkcixuLmNvcmVzPTMwLGFwcGVuZC5hdWM9VFJVRSx6LnRocmVzaG9sZD0wLHVwcmVndWxhdGVkLm9ubHk9VCkKYGBgCgpBbmQgTkIgb25seSBjZWxscwpgYGB7cn0KIyBmYWN0b3IgZm9yIGp1c3QgdGhlIGFkcmVuYWwgU0NQIGNlbGxzCmZhYzMubmIgPC0gY29uYiRnZXREYXRhc2V0UGVyQ2VsbCgpCmZhYzMubmIgPC0gc2V0TmFtZXMocmVwKCdvdGhlcicsbGVuZ3RoKGZhYzMubmIpKSxuYW1lcyhmYWMzLm5iKSkKIyByZW1vdmUgTkIgU0NQLWxpa2UgY2VsbHMKZmFjMy5uYiA8LSBmYWMzLm5iWyFuYW1lcyhmYWMzLm5iKSAlaW4lIGMobmFtZXMoamdycClbamdycD09JzM1J10sIG5hbWVzKGFkci5hbm4pW2Fkci5hbm49PSdTY2h3YW5uIGNlbGxzJ10pXQpmYWMzLm5iW25hbWVzKGZhYzMubmIpICVpbiUgYyhpbnRlcnNlY3QobmFtZXMoYW5ub3QpLG5hbWVzKGpncnApW2pncnA9PSczNyddKSldIDwtICdTQ1AtbGlrZSBOQicKZmFjMy5uYiA8LSBkcm9wbGV2ZWxzKGFzLmZhY3RvcihmYWMzLm5iKSkKdGFibGUoZmFjMy5uYixjb25iJGdldERhdGFzZXRQZXJDZWxsKClbbmFtZXMoZmFjMy5uYildKQpzY2h3YW5uLmRlMy5uYiA8LSBjb25iJGdldERpZmZlcmVudGlhbEdlbmVzKGdyb3Vwcz1mYWMzLm5iLG4uY29yZXM9MzAsYXBwZW5kLmF1Yz1UUlVFLHoudGhyZXNob2xkPTAsdXByZWd1bGF0ZWQub25seT1UKQpgYGAKCkpvaW4gdGFibGVzIGFuZCBzZXBhcmF0ZSBjb21tb24gYW5kIGRpc3RpbmN0IG1hcmtlcnMKYGBge3J9CnJlcXVpcmUobWFncml0dHIpCmRmIDwtIGZ1bGxfam9pbihzY2h3YW5uLmRlMy5hZHJbWydhZHJlbmFsIFNDUCddXSwgc2Nod2Fubi5kZTMubmJbWydTQ1AtbGlrZSBOQiddXSwgYnk9J0dlbmUnLHN1ZmZpeD1jKCcuYWRyJywnLm5iJykpCmRmJG1pbkFVQyA8LSBwbWluKGRmJEFVQy5hZHIsZGYkQVVDLm5iKQpkZiRtYXhBVUMgPC0gcG1heChkZiRBVUMuYWRyLGRmJEFVQy5uYixuYS5ybT1UKQpkZiAlPD4lIGFycmFuZ2UoLW1pbkFVQyxtYXhBVUMpICU+JSBzZWxlY3QoR2VuZSxtaW5BVUMsbWF4QVVDLEFVQy5uYixBVUMuYWRyKQpgYGAKCmBgYHtyfQpoZWFkKGRmLDEwMCkKYGBgCgpgYGB7cn0KdGFpbChkZiwxMDApCmBgYAoKU2hvdyBjb21iaW5lZCBleHByZXNzaW9uIHBhdHRlcm4KYGBge3J9CmdlbmVzLm5iIDwtIHRhaWwoZGZbaXMubmEoZGYkQVVDLmFkciksXSw3KSRHZW5lICM3CgpnZW5lcy5hZHIgPC0gdGFpbChkZltpcy5uYShkZiRBVUMubmIpLF0sNikkR2VuZQpnZW5lcy5hZHIgPC0gdW5pcXVlKGMoZ2VuZXMuYWRyLCdBU0NMMScpKQojZ2VuZXMuY29tbW9uIDwtIGMoaGVhZChkZiwxNCkkR2VuZSwnU09YMTAnLCdBU0NMMScsJ0VHUjInLCdESEgnLCdPQ1Q0JywnSVNMMScpICMxNApnZW5lcy5jb21tb24gPC0gdW5pcXVlKGMoaGVhZChkZiwxMykkR2VuZSwnU09YMTAnLCJGT1hEMyIgLCAiUE1QMiIsICJMR0k0IiwgIlNFTUEzQiIpKSAjMTQKCmdlbmVzIDwtIHVuaXF1ZShjKGdlbmVzLmNvbW1vbixnZW5lcy5hZHIsZ2VuZXMubmIpKQoKI2NlbGwuZmFjdG9yIDwtIGFzLmZhY3RvcihjKHNldE5hbWVzKHJlcCgnTkIgU0NQLWxpa2UnLGxlbmd0aChjMSkpLGMxKSxzZXROYW1lcyhyZXAoImFkcmVuYWwgU0NQIixsZW5ndGgoYzIpKSxjMikpKQpjZWxsLmZhY3RvciA8LSBjKHNldE5hbWVzKGFzLmNoYXJhY3RlcihmYWMzLm5iKSxuYW1lcyhmYWMzLm5iKSksc2V0TmFtZXMoYXMuY2hhcmFjdGVyKGZhYzMuYWRyKSxuYW1lcyhmYWMzLmFkcikpKQpjZWxsLmZhY3RvciA8LSBjZWxsLmZhY3Rvclt1bmlxdWUobmFtZXMoY2VsbC5mYWN0b3IpKV0Kb2MgPC0gbmFtZXMoY2VsbC5mYWN0b3IpW2NlbGwuZmFjdG9yPT0nb3RoZXInXTsgY2VsbC5mYWN0b3Jbb2NdIDwtIHBhc3RlKCdvdGhlcicsYXMuY2hhcmFjdGVyKHRpc3N1ZWZbb2NdKSxzZXA9JyAnKQoKY2VsbC5mYWN0b3IgPC0gZmFjdG9yKGNlbGwuZmFjdG9yLGxldmVscz11bmlxdWUoY2VsbC5mYWN0b3IpW2MoNSwzLDIsNCwxKV0pCgojIG9taXQga2lkbmV5CmNlbGwuZmFjdG9yW2NlbGwuZmFjdG9yPT0nb3RoZXIga2lkbmV5J10gPC0gTkE7IGNlbGwuZmFjdG9yIDwtIGRyb3BsZXZlbHMobmEub21pdChjZWxsLmZhY3RvcikpCgp4YSA8LSBkby5jYWxsKHJiaW5kLGxhcHBseShwYWdvZGEyOjo6c24oZ2VuZXMpLGZ1bmN0aW9uKGdlbmUpIGNvbm9zOjo6Z2V0R2VuZUV4cHJlc3Npb24oY29uYixnZW5lKSkpCmBgYAoKQmVjYXVzZSB0aGUgbnVtYmVycyBvZiBjZWxscyBhcmUgc28gdW5iYWxhbmNlZCwgd2UnbGwgcmFuZG9tbHkgY29sbGFwc2UgbGFyZ2VyIGdyb3VwcyB0byBhcHByb3hpbWF0ZWx5IG1hdGNoIHRoZSBudW1iZXIgb2YgY2VsbHMgaW4gdGhlIHNtYWxsZXIgZ3JvdXBzCmBgYHtyfQp4YyA8LSBkby5jYWxsKGNiaW5kLCB0YXBwbHkoMTpuY29sKHhhKSxjZWxsLmZhY3Rvcltjb2xuYW1lcyh4YSldLGZ1bmN0aW9uKGlpKSB7CiAgYmluLnNpemUgPC0gY2VpbGluZyhsZW5ndGgoaWkpL21pbih0YWJsZShjZWxsLmZhY3RvcikpKQogIG5iaW5zIDwtIGNlaWxpbmcobGVuZ3RoKGlpKS9iaW4uc2l6ZSkKICBpZihiaW4uc2l6ZSA+MSApIHsKICAgIGJmIDwtIHNhbXBsZShyZXAoMTpuYmlucyxiaW4uc2l6ZSkpWzE6bGVuZ3RoKGlpKV0KICAgICN4IDwtIGRvLmNhbGwoY2JpbmQsdGFwcGx5KDE6bGVuZ3RoKGlpKSxhcy5mYWN0b3IoYmYpLGZ1bmN0aW9uKHgpIHJvd01lYW5zKHhhWyxpaVt4XV0pKSkKICAgIHggPC0gZG8uY2FsbChjYmluZCx0YXBwbHkoMTpsZW5ndGgoaWkpLGFzLmZhY3RvcihiZiksZnVuY3Rpb24oeCkgeGFbLHNhbXBsZShpaVt4XSwxKSxkcm9wPUZdKSkKICAgICNjb2xuYW1lcyh4KSA8LSBjb2xuYW1lcyh4YSlbaWldWzE6bmNvbCh4KV0KICAgIHgKICB9IGVsc2UgewogICAgcmV0dXJuKHhhWyxpaV0pCiAgfQp9KSkKYGBgCgoKYGBge3J9CiMgdHJpbQpleHByZXNzaW9uLnF1YW50aWxlIDwtIDAuNzUKeCA8LSBuYS5vbWl0KHhjKQojeCA8LSBuYS5vbWl0KHhbLG5hbWVzKGNlbGwuZmFjdG9yKV0pCnggPC0gdChhcHBseSh4LCAxLCBmdW5jdGlvbih4cCkgewogIAogIGlmKGV4cHJlc3Npb24ucXVhbnRpbGU8MSkgewogICAgICBxcyA8LSBxdWFudGlsZSh4cCxjKDEtZXhwcmVzc2lvbi5xdWFudGlsZSxleHByZXNzaW9uLnF1YW50aWxlKSkKICAgICAgaWYoZGlmZihxcyk9PTApIHsgIyB0b28gbXVjaCwgc2V0IHRvIGFkamFjZW50IHZhbHVlcwogICAgICAgIHhwcyA8LSB1bmlxdWUoeHApCiAgICAgICAgaWYobGVuZ3RoKHhwcyk8MykgeyBxcyA8LSByYW5nZSh4cCkgfSAjIG9ubHkgdHdvIHZhbHVlcywganVzdCB0YWtlIHRoZSBleHRyZW1lcwogICAgICAgIHhwbSA8LSBtZWRpYW4oeHApCiAgICAgICAgaWYoc3VtKHhwPHhwbSkgPiBzdW0oeHA+eHBtKSkgeyAjIG1vcmUgY29tbW9uIHRvIGhhdmUgdmFsdWVzIGJlbG93IHRoZSBtZWRpYW4KICAgICAgICAgIHFzWzFdIDwtIG1heCh4cFt4cDx4cG1dKQogICAgICAgIH0gZWxzZSB7ICMgbW9yZSBjb21tb24gdG8gaGF2ZSB2YWx1ZXMgYWJvdmUgdGhlIG1lZGlhbgogICAgICAgICAgcXNbMl0gPC0gbWluKHhwc1t4cHM+eHBtXSkgIyB0YWtlIHRoZSBuZXh0IG9uZSBoaWdoZXIKICAgICAgICB9CiAgICAgIH0KICAgICAgeHBbeHA8cXNbMV1dIDwtIHFzWzFdCiAgICAgIHhwW3hwPnFzWzJdXSA8LSBxc1syXQogICAgfQogICAgeHAgPC0geHAtbWluKHhwKTsKICAgIGlmKG1heCh4cCk+MCkgeHAgPC0geHAvbWF4KHhwKTsKICAgIHhwCiAgICAKICAjIHFzIDwtIHF1YW50aWxlKHhwLGMoMS1leHByZXNzaW9uLnF1YW50aWxlLGV4cHJlc3Npb24ucXVhbnRpbGUpKQogICMgI3hwW3hwPHFzWzFdXSA8LSBxc1sxXQogICMgaWYocXNbMl0+MCkgewogICMgICB4cFt4cD5xc1syXV0gPC0gcXNbMl0KICAjIH0KICAjIHhwLW1pbih4cCk7CiAgIyB4cHIgPC0gZGlmZihyYW5nZSh4cCkpOwogICMgaWYoeHByPjApIHhwIDwtIHhwL3hwcjsKICAjIHhwCn0pKQoKCmBgYAoKYGBge3IgZmlnLndpZHRoPTQsZmlnLmhlaWdodD03fQpyZXF1aXJlKENvbXBsZXhIZWF0bWFwKQojIGNvbHVtbiBhbm5vdGF0aW9uCmFuIDwtIGRhdGEuZnJhbWUoY2VsbHM9Y2VsbC5mYWN0b3JbY29sbmFtZXMoeCldLHJvdy5uYW1lcyA9IGNvbG5hbWVzKHgpKQojIHJvdyBhbm5vdGF0aW9uCmdwYWwgPC0gYygiY29tbW9uIFNDUCBnZW5lcyI9J2dyZXkzMCcsJ2FkcmVuYWwnPSdncmVlbicsJ05CJz0nb3JhbmdlJyk7CnJkZiA8LSBkYXRhLmZyYW1lKGdlbmVzPXJlcChuYW1lcyhncGFsKSxjKGxlbmd0aChnZW5lcy5jb21tb24pLGxlbmd0aChnZW5lcy5hZHIpLGxlbmd0aChnZW5lcy5uYikpKSxuYW1lPWMoZ2VuZXMuY29tbW9uLGdlbmVzLmFkcixnZW5lcy5uYikpOyAKcmRmIDwtIHJkZlshZHVwbGljYXRlZChyZGYkbmFtZSksXTsKcmRmIDwtIGRhdGEuZnJhbWUoZ2VuZXM9cmRmJGdlbmVzLHJvdy5uYW1lcz1yZGYkbmFtZSkKI3Jvd25hbWVzKHJkZikgPC0gYyhnZW5lcy5jb21tb24sZ2VuZXMuYWRyLGdlbmVzLm5iKTsgCnJkZiA8LSByZGZbcm93bmFtZXMoeCksLGRyb3A9Rl0KcmEgPC0gQ29tcGxleEhlYXRtYXA6OkhlYXRtYXBBbm5vdGF0aW9uKGRmPXJkZix3aGljaD0ncm93JyxzaG93X2Fubm90YXRpb25fbmFtZT1GQUxTRSwgc2hvd19sZWdlbmQ9RkFMU0UsIGJvcmRlcj1ULGNvbD1saXN0KGdlbmVzPWdwYWwpKQoKcmEgPC0gQ29tcGxleEhlYXRtYXA6OkhlYXRtYXBBbm5vdGF0aW9uKGdlbmVzPWFubm9fYmxvY2soZ3A9Z3BhcihmaWxsPU5BLGNvbD1OQSksbGFiZWxzPW5hbWVzKGdwYWxbbGV2ZWxzKHJkZiRnZW5lcyldKSxsYWJlbHNfZ3AgPSBncGFyKGZvbnRzaXplPTE2KSApLHdoaWNoPSdyb3cnLHNob3dfYW5ub3RhdGlvbl9uYW1lPUZBTFNFLCBzaG93X2xlZ2VuZD1GQUxTRSwgYm9yZGVyPVQpCgojdGEgPC0gQ29tcGxleEhlYXRtYXA6OkhlYXRtYXBBbm5vdGF0aW9uKGRmPWFuLGJvcmRlcj1ULHNob3dfbGVnZW5kID0gVCxjb2w9bGlzdChjZWxscz1jKCdhZHJlbmFsIFNDUCc9J2RhcmtvcmNoaWQ0JywnU0NQLWxpa2UgTkInPSdyZWQnLCdvdGhlciBOQic9J29yYW5nZScsJ290aGVyIGFkcmVuYWwnPSdncmVlbicsJ290aGVyIGtpZG5leSc9J3JveWFsYmx1ZTMnKSkpCgpncGFsIDwtIGMoJ2FkcmVuYWwgU0NQJz0nZGFya29yY2hpZDQnLCdTQ1AtbGlrZSBOQic9J3JlZCcsJ290aGVyIE5CJz0nb3JhbmdlJywnb3RoZXIgYWRyZW5hbCc9J2dyZWVuJyk7CnRhIDwtIENvbXBsZXhIZWF0bWFwOjpIZWF0bWFwQW5ub3RhdGlvbihjZWxscz1hbm5vX2Jsb2NrKGdwPWdwYXIoZmlsbD1OQSxjb2w9TkEpLCBsYWJlbHM9bmFtZXMoZ3BhbCksIGxhYmVsc19yb3QgPSAzNSwgIGxhYmVsc19ncCA9IGdwYXIoanVzdD0tMSwgZm9udHNpemU9MTQpKSkKCiNDb21wbGV4SGVhdG1hcDo6SGVhdG1hcCh4LG5hbWU9J0V4cHJlc3Npb24nLGNvbD1jb2xvclJhbXBQYWxldHRlKGMoJ2RvZGdlcmJsdWUxJywnZ3JleTk1JywnaW5kaWFucmVkMScpKSgxMDI0KSwgbGVmdF9hbm5vdGF0aW9uPXJhLCBjbHVzdGVyX3Jvd3MgPSBGLCBjbHVzdGVyX2NvbHVtbnMgPSBGQUxTRSxzaG93X3Jvd19uYW1lcyA9IFQsIHNob3dfY29sdW1uX25hbWVzID0gRix0b3BfYW5ub3RhdGlvbiA9IHRhLGJvcmRlcj1UKQpzZXQuc2VlZCgwKQpobSA8LSBDb21wbGV4SGVhdG1hcDo6SGVhdG1hcCh4LG5hbWU9J0V4cHJlc3Npb24nLGNvbD1jb2xvclJhbXBQYWxldHRlKGMoJ2RvZGdlcmJsdWUxJywnZ3JleTk1JywnaW5kaWFucmVkMScpKSgxMDI0KSwgbGVmdF9hbm5vdGF0aW9uPXJhLCBjbHVzdGVyX3Jvd3MgPSBGLCBjbHVzdGVyX2NvbHVtbnMgPSBGQUxTRSxzaG93X3Jvd19uYW1lcyA9IFQsIHNob3dfY29sdW1uX25hbWVzID0gRix0b3BfYW5ub3RhdGlvbiA9IHRhLGJvcmRlcj1ULGNvbHVtbl9zcGxpdD1hbltjb2xuYW1lcyh4KSwxXSwgcm93X3NwbGl0PXJkZlssMV0sIHJvd19nYXAgPSB1bml0KDEsICJtbSIpLCBjb2x1bW5fZ2FwID0gdW5pdCgxLCAibW0iKSxyb3dfbmFtZXNfZ3AgPSBncmlkOjpncGFyKGZvbnRzaXplID0gOCksY29sdW1uX3RpdGxlID0gTlVMTCwgcm93X3RpdGxlID0gTlVMTCwgaGVhdG1hcF9sZWdlbmRfcGFyYW0gPSBsaXN0KGxlZ2VuZF9kaXJlY3Rpb249J2hvcml6b250YWwnKSx1c2VfcmFzdGVyID0gVCxyYXN0ZXJfZGV2aWNlID0gIkNhaXJvUE5HIikKaG1wIDwtIGRyYXcoaG0sIGhlYXRtYXBfbGVnZW5kX3NpZGUgPSAiYm90dG9tIikKcGRmKGZpbGU9J3NjcC5tYXJrZXJzMi5wZGYnLHdpZHRoPTQsaGVpZ2h0PTcpOyBwcmludChobXApOyBkZXYub2ZmKCk7CmBgYAoKCgojIyMgQ2VsbCB0eXBlIHNpbWlsYXJpdHkgbWF0cml4CgpMZXQncyBnbyB3aXRoIHRoZSBzaW1wbGVzdCB0aGluZwpgYGB7cn0Kc291cmNlKCJ+cGtoYXJjaGVua28vbS9wYXZhbi9ETEkvY29ucDIuciIpCmYxIDwtIHNldE5hbWVzKGFzLmNoYXJhY3RlcihhZHIuYW5uKSxuYW1lcyhhZHIuYW5uKSkKI2YxW2YxICVpbiUgYygiTW9ub2N5dGVzIiwiTWFzdCIsJ21EQycpXSA8LSAnTXllbG9pZCcKZjIgPC0gc2V0TmFtZXMoYXMuY2hhcmFjdGVyKGFubm90KSxuYW1lcyhhbm5vdCkpCmYyW2YyICVpbiUgYygiQiIsIklMQzMiLCdOSycsJ3BEQycsJ1BsYXNtYScsJ1RjeXRvJywnVGgnLCdUcmVnJyldIDwtICdMeW1waG9pZCcKZjJbZjIgJWluJSBjKCJNb25vY3l0ZXMiLCJNYXN0IiwnbURDJywnTWFjcm9waGFnZXMnKV0gPC0gJ015ZWxvaWQnCgpncm91cHMgPC0gYXMuZmFjdG9yKGMoc2V0TmFtZXMocGFzdGUoZjEsJ2Fkcicsc2VwPSc6JyksbmFtZXMoZjEpKSxzZXROYW1lcyhwYXN0ZShmMiwnbmInLHNlcD0nOicpLG5hbWVzKGYyKSkpKQpncm91cHNbZ3JlcCgnRXJ5dGhyb2lkfE15ZWxvaWR8THltcGgnLGdyb3VwcyldIDwtIE5BOyAKZ3JvdXBzW2dyZXAoJ0tpZG5leXxDb3J0ZXgnLGdyb3VwcyldIDwtIE5BOyAKZ3JvdXBzIDwtIGRyb3BsZXZlbHMobmEub21pdChncm91cHMpKQp0YWJsZShncm91cHMpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAojZ3JvdXBzIDwtIHVubGlzdChsaXN0KGFubm90LGFkci5hbm4pKQp0Y2QgPC0gY2x1c3Rlci5leHByZXNzaW9uLmRpc3RhbmNlcyhjb25iLGdyb3Vwcz1ncm91cHMsZGlzdD0nSlMnLHVzZS5hZ2dyZWdhdGVkLm1hdHJpY2VzID0gVCkKc29ydCh0Y2RbZ3JlcGwoIlNDUC1saWtlOm5iIixyb3duYW1lcyh0Y2QpKSxncmVwbCgiOmFkciIsY29sbmFtZXModGNkKSldLGRlYz1UKQpgYGAKCgpVc2luZyBKUyBkaXN0YW5jZQpgYGB7ciBmaWcud2lkdGg9NC41LGZpZy5oZWlnaHQ9NX0KY2Rpc3QgPC0gdGNkW2dyZXBsKCI6bmIiLHJvd25hbWVzKHRjZCkpLGdyZXBsKCI6YWRyIixjb2xuYW1lcyh0Y2QpKV0KI2NkaXN0IDwtIHRjZFtsZXZlbHMoYW5ub3QpLGxldmVscyhhZHIuYW5uKV0KI2NkaXN0IDwtIHQoYXBwbHkoY2Rpc3QsMSxmdW5jdGlvbih4KSAoeC1taW4oeCkpL21heCh4KSkpCnJvd25hbWVzKGNkaXN0KSA8LSBnc3ViKCI6LioiLCIiLHJvd25hbWVzKGNkaXN0KSkKY29sbmFtZXMoY2Rpc3QpIDwtIGdzdWIoIjouKiIsIiIsY29sbmFtZXMoY2Rpc3QpKQpjb2wgPC0gY29sb3JSYW1wUGFsZXR0ZShjKCd3aGl0ZScsJ2RhcmtibHVlJykpKDEwMjQpCiNjb2wgPC0gY2lyY2xpemU6OmNvbG9yUmFtcDIoYygwLjcsIDAuOSksIGMoIndoaXRlIiwgImRhcmtibHVlIikpCkhlYXRtYXAoMS1jZGlzdCxjb2w9Y29sLCBjbHVzdGVyX3Jvd3MgPSBULGNsdXN0ZXJfY29sdW1ucyA9IFQsYm9yZGVyPVQpCmBgYAoKQ29ycmVsYXRpb24tYmFzZWQKYGBge3J9CnRjIDwtIGNvbm9zOjo6cmF3TWF0cmljZXNXaXRoQ29tbW9uR2VuZXMoY29uYikgJT4lIAogIGxhcHBseShjb25vczo6OmNvbGxhcHNlQ2VsbHNCeVR5cGUsIGdyb3Vwcz1hcy5mYWN0b3IoZ3JvdXBzKSwgbWluLmNlbGwuY291bnQ9MCkgJT4lCiAgYWJpbmQoYWxvbmc9MykgJT4lCiAgYXBwbHkoYygxLDIpLHN1bSxuYS5ybT1UKQpsaWIuc2l6ZS5zY2FsZSA8LSAxZTY7CnRjIDwtIGxvZzEwKHQodGMvcG1heCgxLHJvd1N1bXModGMpKSkqbGliLnNpemUuc2NhbGUrMSkKCiN0Y2QgPC0gY29yKHRjKQoKYGBgCgpVc2luZyB0b3Agb3ZlcmRpc3BlcnNlZCBnZW5lcwpgYGB7cn0KZ25zIDwtIGFwMiRtaXNjJHZhcmluZm8gJT4lIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKCdnZW5lJykgJT4lIGFycmFuZ2UoLXF2KSAlPiUgaGVhZCgyZTMpICU+JSAnJCcoJ2dlbmUnKQp0Y2QgPC0gY29yKHRjW3Jvd25hbWVzKHRjKSAlaW4lIGducyxdKQojdGNkIDwtIGNvcih0YykKc29ydCh0Y2RbZ3JlcGwoIlNDUC1saWtlOm5iIixyb3duYW1lcyh0Y2QpKSxncmVwbCgiOmFkciIsY29sbmFtZXModGNkKSldLGRlYz1UKQpgYGAKCgpgYGB7ciBmaWcud2lkdGg9NC41LGZpZy5oZWlnaHQ9NC4yfQpjZGlzdCA8LSB0Y2RbZ3JlcGwoIjpuYiIscm93bmFtZXModGNkKSksZ3JlcGwoIjphZHIiLGNvbG5hbWVzKHRjZCkpXQojY2Rpc3QgPC0gdGNkW2xldmVscyhhbm5vdCksbGV2ZWxzKGFkci5hbm4pXQpyb3duYW1lcyhjZGlzdCkgPC0gZ3N1YigiOi4qIiwiIixyb3duYW1lcyhjZGlzdCkpCmNvbG5hbWVzKGNkaXN0KSA8LSBnc3ViKCI6LioiLCIiLGNvbG5hbWVzKGNkaXN0KSkKY29sIDwtIGNvbG9yUmFtcFBhbGV0dGUoYygnd2hpdGUnLCdkYXJrYmx1ZScpKSgxMDI0KQpjb2wgPC0gY2lyY2xpemU6OmNvbG9yUmFtcDIoYygwLjQsIDAuNyksIGMoIndoaXRlIiwgImRhcmtibHVlIikpCmNkaXN0IDwtIGNkaXN0W2MoNCw2LDUsMSwyLDMpLGMoMSw1LDQsMiwzKV0KY20gPC0gSGVhdG1hcChjZGlzdCxjb2w9Y29sLCBjbHVzdGVyX3Jvd3MgPSBGLGNsdXN0ZXJfY29sdW1ucyA9IEYsYm9yZGVyPVQsbmFtZT0nY29ycmVsYXRpb24nLGNvbHVtbl9uYW1lc19zaWRlPSd0b3AnLGNvbHVtbl90aXRsZSA9ICdBZHJlbmFsIGNlbGwgdHlwZXMnLHJvd190aXRsZT0nTkIgY2VsbCB0eXBlcycsY29sdW1uX3RpdGxlX3NpZGUgPSAnYm90dG9tJykKcGRmKGZpbGU9J2FkcmVuYWwubWF0Y2gucGRmJyx3aWR0aD00LGhlaWdodD0zLjkpOyBwcmludChjbSk7IGRldi5vZmYoKTsKY20KYGBgCgoKCgoKCgoKCgojIERFQlVHCgpgYGB7cn0KeCA8LSBuYS5vbWl0KHhhKQp4IDwtIG5hLm9taXQoeFssbmFtZXMoY2VsbC5mYWN0b3IpXSkKeCA8LSB4PjAKY2xhc3MoeCkgPC0gJ251bWVyaWMnCmBgYAoKCmBgYHtyIGZpZy53aWR0aD02LGZpZy5oZWlnaHQ9MTB9CmFubm90IDwtIGRhdGEuZnJhbWUoY2x1c3RlcnM9Y2VsbC5mYWN0b3JbY29sbmFtZXMoeCldLHJvdy5uYW1lcyA9IGNvbG5hbWVzKHgpKQp0YSA8LSBDb21wbGV4SGVhdG1hcDo6SGVhdG1hcEFubm90YXRpb24oZGY9YW5ub3QsYm9yZGVyPVQsc2hvd19sZWdlbmQgPSBUKQpDb21wbGV4SGVhdG1hcDo6SGVhdG1hcCh4LGNvbD1jb2xvclJhbXBQYWxldHRlKGMoJ3doaXRlJywnZ3JheTIwJykpKDEwMjQpLGNsdXN0ZXJfcm93cyA9IEYsIGNsdXN0ZXJfY29sdW1ucyA9IEZBTFNFLHNob3dfcm93X25hbWVzID0gVCwgc2hvd19jb2x1bW5fbmFtZXMgPSBGLHRvcF9hbm5vdGF0aW9uID0gdGEsYm9yZGVyPVQpCmBgYAoKCmBgYHtyIGZpZy53aWR0aD0xNCxmaWcuaGVpZ2h0PTd9CnNvdXJjZSgifi9tL3AyL2Nvbm9zL1IvcGxvdC5SIikKc2ZhYyA8LSBhcy5mYWN0b3IoZmFjMik7IHNmYWMgPC0gZHJvcGxldmVscyhzZmFjW3VubGlzdCh0YXBwbHkoMTpsZW5ndGgoZmFjKSxmYWMsc2FtcGxlLG1pbih0YWJsZShmYWMpKSkpXSkKcHAgPC0gcGxvdERFaGVhdG1hcChjb25iLHNmYWMsc2Nod2Fubi5kZTIsbi5nZW5lcy5wZXIuY2x1c3RlciA9IDMwICxzaG93LmdlbmUuY2x1c3RlcnM9VCxyb3cubGFiZWwuZm9udC5zaXplID0gNyxjb2x1bW4ubWV0YWRhdGEgPSBsaXN0KHRpc3N1ZT10aXNzdWVmKSkKI3BkZihmaWxlPSdhZHIuaGVhdG1hcC5wZGYnLHdpZHRoPTcsaGVpZ2h0PTE1KTsgcHJpbnQocHApOyBkZXYub2ZmKCk7CnBwCmBgYAoKCgpUcnkgYWRyZW5hbCBTQ1AgbWFya2VycwpgYGB7cn0KYWRyLmRlIDwtIGFwMiRnZXREaWZmZXJlbnRpYWxHZW5lcyh0eXBlPSdQQ0EnLGdyb3Vwcz1hZHIuYW5uLGFwcGVuZC5hdWM9VFJVRSx6LnRocmVzaG9sZD0wLHVwcmVndWxhdGVkLm9ubHk9VCkKYGBgCgpgYGB7ciBmaWcud2lkdGg9NixmaWcuaGVpZ2h0PTh9CnNvdXJjZSgifi9tL3AyL2Nvbm9zL1IvcGxvdC5SIikKcHAgPC0gcGxvdERFaGVhdG1hcChhcDIsYWRyLmFubixhZHIuZGUsbi5nZW5lcy5wZXIuY2x1c3RlciA9IDEwICxzaG93LmdlbmUuY2x1c3RlcnM9VCxyb3cubGFiZWwuZm9udC5zaXplID0gNixvcmRlci5jbHVzdGVycyA9IFQpCiNwZGYoZmlsZT0nYWRyLmhlYXRtYXAucGRmJyx3aWR0aD03LGhlaWdodD0xNSk7IHByaW50KHBwKTsgZGV2Lm9mZigpOwpwcApgYGAKCgpgYGB7ciBmaWcud2lkdGg9NixmaWcuaGVpZ2h0PTh9CnNvdXJjZSgifi9tL3AyL2Nvbm9zL1IvcGxvdC5SIikKc2ZhYyA8LSBhZHIuYW5uOyBzZmFjIDwtIGRyb3BsZXZlbHMoc2ZhY1tzZmFjICVpbiUgYygiQ2hyb21hZmZpbiBjZWxscyIsIlNjaHdhbm4gY2VsbHMiLCJTeW1wYXRob2JsYXN0cyIpXSkKcHAgPC0gcGxvdERFaGVhdG1hcChhcDIsc2ZhYyxhZHIuZGUsbi5nZW5lcy5wZXIuY2x1c3RlciA9IDMwICxzaG93LmdlbmUuY2x1c3RlcnM9VCxyb3cubGFiZWwuZm9udC5zaXplID0gNikKI3BkZihmaWxlPSdhZHIuaGVhdG1hcC5wZGYnLHdpZHRoPTcsaGVpZ2h0PTE1KTsgcHJpbnQocHApOyBkZXYub2ZmKCk7CnBwCmBgYAoKYGBge3J9CgpgYGAKCklkZW50aWZ5IGdlbmVzIHRoYXQgZGlzdGluZ3Vpc2ggdGhlIHR3byBwb3B1bGF0aW9ucwpgYGB7cn0KZC5mYWMgPC0gY29uYiRnZXREYXRhc2V0UGVyQ2VsbCgpCmQuZmFjIDwtIHNldE5hbWVzKHJlcCgnb3RoZXInLGxlbmd0aChkLmZhYykpLG5hbWVzKGQuZmFjKSkKZC5mYWNbbmFtZXMoZC5mYWMpICVpbiUgYyhuYW1lcyhhZHIuYW5uKVthZHIuYW5uPT0nU2Nod2FubiBjZWxscyddKV0gPC0gJ25vcm1hbCBTQ1BzJwpkLmZhY1tuYW1lcyhkLmZhYykgJWluJSBjKG5hbWVzKGFubm90KVthbm5vdD09J1NDUC1saWtlJ10pXSA8LSAndHVtb3IgU0NQcycKdHVtLm5vcm0uZGUgPC0gY29uYiRnZXREaWZmZXJlbnRpYWxHZW5lcyhncm91cHM9ZC5mYWMsIG4uY29yZXM9MzAsYXBwZW5kLmF1Yz1UUlVFLHoudGhyZXNob2xkPTAsdXByZWd1bGF0ZWQub25seT1UKQpgYGAKCkNvbWJpbmUgaW4gYSBoZWF0bWFwOgoKCgojIyMgTWlzYyBzdHVmZgpgYGB7ciBmaWcud2lkdGg9MTUsZmlnLmhlaWdodD01fQoKcDEgPC0gbmNkY29uJHBsb3RHcmFwaChhbHBoYT0wLjAzLHNpemU9MC41LGdyb3Vwcz1hbm5vdCxwbG90Lm5hPUYsbWFyay5ncm91cHM9VCkKcDIgPC0gbmNkY29uJHBsb3RHcmFwaChhbHBoYT0wLjAzLHNpemU9MC41LGdlbmU9J1BSUlgxJyxwbG90Lm5hPUYsbWFyay5ncm91cHM9VCkKcDMgPC0gbmNkY29uJHBsb3RHcmFwaChhbHBoYT0wLjAzLHNpemU9MC41LGdlbmU9J1BIT1gyQicscGxvdC5uYT1GLG1hcmsuZ3JvdXBzPVQpCnBsb3RfZ3JpZChwbG90bGlzdD1saXN0KHAxLHAyLHAzKSxucm93PTEpCmBgYApBcyBmYWN0b3JzCmBgYHtyfQphc2NsMWUgPC0gY29ub3M6OjpnZXRHZW5lRXhwcmVzc2lvbihuY2Rjb24sJ0FTQ0wxJykKcGhveDJiZSA8LSBjb25vczo6OmdldEdlbmVFeHByZXNzaW9uKG5jZGNvbiwnUEhPWDJCJykKcHJyeDJlIDwtIGNvbm9zOjo6Z2V0R2VuZUV4cHJlc3Npb24obmNkY29uLCdQUlJYMicpCnBycngxZSA8LSBjb25vczo6OmdldEdlbmVFeHByZXNzaW9uKG5jZGNvbiwnUFJSWDEnKQpzb3gxMGUgPC0gY29ub3M6OjpnZXRHZW5lRXhwcmVzc2lvbihuY2Rjb24sJ1NPWDEwJykKZm94ZDNlIDwtIGNvbm9zOjo6Z2V0R2VuZUV4cHJlc3Npb24obmNkY29uLCdGT1hEMycpCgpkYyA8LSBzb3gxMGU+MCAmIHBob3gyYmU+MAp4IDwtIGxhcHBseShuY2Rjb24kc2FtcGxlcyxmdW5jdGlvbihkKSBkW1snbWlzYyddXVtbJ3Jhd0NvdW50cyddXSkKYGBgCgpgYGB7ciBmaWcud2lkdGg9MTUsZmlnLmhlaWdodD01fQpwMSA8LSBuY2Rjb24kcGxvdEdyYXBoKGFscGhhPTAuMDMsc2l6ZT0wLjUsZ3JvdXBzPWFubm90LHBsb3QubmE9RixtYXJrLmdyb3Vwcz1UKQpwMiA8LSBuY2Rjb24kcGxvdEdyYXBoKGFscGhhPTAuMDMsc2l6ZT0wLjUsZ2VuZT0nU09YMTAnLHBsb3QubmE9RixtYXJrLmdyb3Vwcz1UKQpwMyA8LSBuY2Rjb24kcGxvdEdyYXBoKGFscGhhPTEsc2l6ZT0yLGdyb3Vwcz1zb3gxMGU+MCAmIHBob3gyYmU+MCxwbG90Lm5hPUYsbWFyay5ncm91cHM9RixwYWxldHRlPWMoYWRqdXN0Y29sb3IoJ2dyZXk5MCcsYWxwaGE9MC4wMSksJ3JlZCcpKQpwbG90X2dyaWQocGxvdGxpc3Q9bGlzdChwMSxwMixwMyksbnJvdz0xKQpgYGAKCgoKYGBge3IgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NX0KbmNkY29uJHNhbXBsZXMkTkIyNiRwbG90RW1iZWRkaW5nKGdyb3Vwcz1hbm5vdCx0eXBlPSdQQ0EnLG1hcmsuY2x1c3RlcnMgPSBULG1hcmsuY2x1c3Rlci5jZXggPSAxLGVtYmVkZGluZ1R5cGU9J3RTTkUnKQpgYGAKCmBgYHtyIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTV9CiNkYyA8LSBzb3gxMGU+MCAmIGFzY2wxZT4wCmRjIDwtIHNveDEwZT4wICYgcGhveDJiZT4wCmRjIDwtIGZveGQzZT4wICYgcGhveDJiZT4wCmRjIDwtIHNveDEwZT4wICYgcHJyeDJlPjAKZGMgPC0gc294MTBlPjAgJiBwcnJ4MWU+MApkYyA8LSBwaG94MmJlPjAgJiBwcnJ4MWU+MApkYyA8LSBwaG94MmJlPjAuMyAmIHBycngxZT4wLjMgJiBzb3gxMGU+MAp0YWJsZShkYykKbmFtZXMoZGMpW2RjXQplbSA8LSBuY2Rjb24kc2FtcGxlcyROQjI2JGVtYmVkZGluZ3MkUENBW1syXV0KcGxvdChlbVssMV0sZW1bLDJdLHBjaD0xOSxjZXg9MC4yLGNvbD1hZGp1c3Rjb2xvcigxLGFscGhhPTAuMDEpKQp2YyA8LSBuYW1lcyhkYylbZGNdCnBvaW50cyhlbVtyb3duYW1lcyhlbSkgJWluJSB2YywxXSxlbVtyb3duYW1lcyhlbSkgJWluJSB2YywyXSxjb2w9MikKYGBgCgoKYGBge3IgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NX0KdmMgPC0gbmFtZXMoZGMpW2RjXTsgdmMgPC0gdmNbZ3JlcGwoJ15OQicsdmMpXQpkZiA8LSBkYXRhLmZyYW1lKHBob3gyYj1waG94MmJlW3ZjXSxwcnJ4MT1wcnJ4MWVbdmNdLHR5cGU9YW5ub3RbdmNdLGNlbGw9dmMpCmdncGxvdChkZixhZXMoeD1waG94MmIseT1wcnJ4MSxjb2xvdXI9dHlwZSkpICsgZ2VvbV9wb2ludCgpCmBgYAoKCmBgYHtyIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTV9CnBsb3QobmNkY29uJGVtYmVkZGluZ1ssMV0sbmNkY29uJGVtYmVkZGluZ1ssMl0scGNoPTE5LGNleD0wLjIsY29sPWFkanVzdGNvbG9yKDEsYWxwaGE9MC4wMSkpCnZjIDwtIG5hbWVzKGRjKVtkY10KcG9pbnRzKG5jZGNvbiRlbWJlZGRpbmdbcm93bmFtZXMobmNkY29uJGVtYmVkZGluZykgJWluJSB2YywxXSxuY2Rjb24kZW1iZWRkaW5nW3Jvd25hbWVzKG5jZGNvbiRlbWJlZGRpbmcpICVpbiUgdmMsMl0sY29sPTIpCmBgYAoKYGBge3IgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NX0KZW1iIDwtIGFjb24kZW1iZWRkaW5nCnBsb3QoZW1iWywxXSxlbWJbLDJdLHBjaD0xOSxjZXg9MC4yLGNvbD1hZGp1c3Rjb2xvcigxLGFscGhhPTAuMDEpKQp2YyA8LSBuYW1lcyhkYylbZGNdCnRhYmxlKHZjICVpbiUgcm93bmFtZXMoZW1iKSkKcG9pbnRzKGVtYltyb3duYW1lcyhlbWIpICVpbiUgdmMsMV0sZW1iW3Jvd25hbWVzKGVtYikgJWluJSB2YywyXSxjb2w9MikKYGBgCgpgYGB7ciBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD01fQpwbG90KG5jZGNvbiRlbWJlZGRpbmdbLDFdLG5jZGNvbiRlbWJlZGRpbmdbLDJdLHBjaD0xOSxjZXg9MC4yLGNvbD1zY2NvcmU6OjpmYWMyY29sKGFubm90W21hdGNoKHJvd25hbWVzKG5jZGNvbiRlbWJlZGRpbmcpLG5hbWVzKGFubm90KSldKSkKYGBgCgpgYGB7cn0KYWNvbiA8LSByZWFkUkRTKCJ+cGtoYXJjaGVua28vbS9uaW5pYi9OQi9hY29uLnJkcyIpCmBgYAoKYGBge3IgZmlnLndpZHRoPTEwLGZpZy5oZWlnaHQ9My41fQojcDEgPC0gYWNvbiRwbG90R3JhcGgoYWxwaGE9MC4wMSxzaXplPTAuMixncm91cHM9Y29uJGdldERhdGFzZXRQZXJDZWxsKCkpCnAxIDwtIGFjb24kcGxvdEdyYXBoKGFscGhhPTAuMDEsc2l6ZT0wLjUsZ3JvdXBzPXNjcnVibGV0ZixtYXJrLmdyb3Vwcz1GLHBsb3QubmE9RixwYWxldHRlPWZ1bmN0aW9uKG4pIGMoJ2dyYXknLCdyZWQnKSx0aXRsZT0iU2NydWJsZXQiKQpwMiA8LSBhY29uJHBsb3RHcmFwaChhbHBoYT0wLjAxLHNpemU9MC41LGdyb3Vwcz1iYW5ub3QscGxvdC5uYT1GLG1hcmsuZ3JvdXBzPVQsdGl0bGU9IkFubm90YXRpb25zIikKcDMgPC0gYWNvbiRwbG90R3JhcGgoYWxwaGE9MC4wMSxzaXplPTAuNSxnZW5lPSJNS0k2NyIsdGl0bGU9Ik1LSTY3IikKcHAgPC0gcGxvdF9ncmlkKHBsb3RsaXN0PWxpc3QocDEscDMscDIpLG5yb3c9MSkKcHAKYGBg